37 votes

Ajout d'une variable d'instance à une classe en Ruby

Comment puis-je ajouter une variable d'instance d'une classe définie à exécution, et, plus tard, d'obtenir et de définir sa valeur à partir de l'extérieur de la classe?

Edit: Il semble que j'ai besoin de préciser que je suis à la recherche d'un métaprogrammation solution qui me permet de modifier l'instance de la classe au moment de l'exécution au lieu de modifier le code source qui a été définie à l'origine de la classe. Quelques solutions expliquer comment déclarer des variables d'instance dans les définitions de classe, mais ce n'est pas ce que je suis en demandant au sujet. Désolé pour la confusion.

65voto

Gordon Wilson Points 14721

Ruby fournit des méthodes pour cela, instance_variable_get et instance_variable_set . ( docs )

Vous pouvez créer et assigner une nouvelle variable d'instance comme ceci:

 >> foo = Object.new
=> #<Object:0x2aaaaaacc400>

>> foo.instance_variable_set(:@bar, "baz")
=> "baz"

>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">
 

15voto

Mike Stone Points 21293

Vous pouvez utiliser l'attribut accesseurs:

class Array
  attr_accessor :var
end

Maintenant, vous pouvez y accéder via:

array = []
array.var = 123
puts array.var


Mise à jour:

Notez que vous pouvez également utiliser attr_reader ou attr_writer pour définir les méthodes de lecture ou setters... ou vous pouvez définir manuellement:

class Array
  attr_reader :getter_only_method
  attr_writer :setter_only_method

  # Manual definitions equivalent to using attr_reader/writer/accessor
  def var
    @var
  end

  def var=(value)
    @var = value
  end
end


Mise À Jour:

Vous pouvez également utiliser singleton méthodes si vous voulez juste qu'il défini sur une seule instance:

array = []

def array.var
  @var
end

def array.var=(value)
  @var = value
end

array.var = 123
puts array.var


Pour info, en réponse à l'observation sur cette réponse, le singleton méthode fonctionne très bien, et le suivant en est la preuve:

irb(main):001:0> class A
irb(main):002:1>   attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1>   @b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0>

Comme vous pouvez le voir, le singleton méthode setit mettra le même champ, @b, comme celle qui est définie à l'aide de la attr_accessor... donc un singleton est une méthode parfaitement valable à cette question.

15voto

Mike Stone Points 21293

@Readonly

Si votre utilisation de la classe "Monobjet" est un usage de la classe ouverte, alors s'il vous plaît noter que vous êtes la redéfinition de la méthode initialize.

En Ruby, il n'y a pas une telle chose comme la surcharge... seulement dominante, ou la redéfinition... en d'autres termes, il ne peut être de 1 instance de toute méthode, donc, si vous le redéfinir, il est redéfini... et la méthode initialize est pas différent (même si c'est ce que la nouvelle méthode de la Classe des objets d'usage).

Donc, jamais de redéfinir une méthode existante sans crénelage d'abord... au moins si vous voulez accéder à la définition originelle. Et la redéfinition de la méthode initialize d'un inconnu de classe peut être très risqué.

En tout cas, je pense que j'ai une solution beaucoup plus simple pour vous, qui utilise le réel métaclasse pour définir singleton méthodes:

m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second

Vous pouvez utiliser à la fois la métaclasse et ouvrir des classes pour obtenir encore plus difficile et faire quelque chose comme:

class MyObject
  def metaclass
    class << self
      self
    end
  end

  def define_attributes(hash)
    hash.each_pair { |key, value|
      metaclass.send :attr_accessor, key
      send "#{key}=".to_sym, value
    }
  end
end

m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })

Le ci-dessus est essentiellement d'exposer la métaclasse via le "métaclasse" méthode, puis en l'utilisant dans define_attributes pour définir dynamiquement un ensemble d'attributs avec attr_accessor, puis en l'invoquant l'attribut setter par la suite avec la valeur associée dans la table de hachage.

En Ruby, vous pouvez faire preuve de créativité et faire la même chose de plusieurs manières différentes ;-)


Pour info, au cas où vous ne le saviez pas, à l'aide de la métaclasse comme je l'ai fait signifie que vous êtes en agissant uniquement sur l'instance donnée de l'objet. Ainsi, en invoquant define_attributes ne définir des attributs pour ce cas particulier.

Exemple:

m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!

2voto

webmat Points 13359

La réponse de Mike Stone est déjà assez complète, mais j'aimerais ajouter un peu de détail.

Vous pouvez modifier votre classe à tout moment, même après la création de certaines instances, et obtenir les résultats souhaités. Vous pouvez l'essayer dans votre console:

 s1 = 'string 1'
s2 = 'string 2'

class String
  attr_accessor :my_var
end

s1.my_var = 'comment #1'
s2.my_var = 'comment 2'

puts s1.my_var, s2.my_var
 

2voto

Mike Stone Points 21293

Les autres solutions fonctionnent parfaitement, mais voici un exemple d'utilisation de define_method, si vous sont enfer plié sur la non utilisation de l'ouverture des classes... il va définir le "var" à la variable de la classe array... mais notez qu'il est ÉQUIVALENT à l'utilisation d'une classe ouverte... l'avantage est que vous pouvez le faire pour un inconnu de la classe (donc un objet de la classe, plutôt que d'ouvrir une classe spécifique)... aussi define_method va travailler à l'intérieur d'une méthode, alors que vous ne pouvez pas ouvrir une classe dans une méthode.

array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }

Et voici un exemple de son utilisation... à noter que array2, un DIFFÉRENT matrice a aussi les méthodes, donc, si ce n'est pas ce que vous voulez, vous voulez probablement singleton méthodes qui je l'ai expliqué dans un autre post.

irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { @var }
=> #<Proc:0x00007f289ccb62b0@(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value }
=> #<Proc:0x00007f289cc9fa88@(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X