class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
J'ai créé la classe ci-dessus. Il n'imprime rien. Je pensais que la variable d'instance @hello a été définie lors de la déclaration de classe. Mais lorsque j'appelle la méthode d'affichage, la sortie est "nulle". Quelle est la bonne façon de procéder?
Les variables d'instance en Ruby peuvent être un peu déroutantes lors de la première utilisation de Ruby, surtout si vous êtes habitué à un autre OO langage comme Java.
Vous ne pouvez pas simplement déclarer une variable d'instance.
Une des choses les plus importantes à savoir sur les variables d'instance dans Ruby, à part la notation avec un préfixe @, est que elles prennent vie la première fois qu'elles sont affectées.
class Hello
def create_some_state
@hello = "hello"
end
end
h = Hello.new
p h.instance_variables
h.create_some_state
p h.instance_variables
# Output
[]
["@hello"]
Vous pouvez utiliser la méthode Object#instance_variables
Pour répertorier toutes les variables d'instance d'un objet.
Normalement, vous "déclarez" et initialisez toutes les variables d'instance dans la méthode initialize. Une autre façon de documenter clairement les variables d'instance qui devraient être accessibles au public est d'utiliser les méthodes du module attr_accessor
(Lecture/écriture), attr_writer
(Écriture) et attr_reader
(Lecture) . Ces méthodes synthétiseront différentes méthodes d'accesseur pour la variable d'instance répertoriée.
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
La variable d'instance n'est toujours pas créée tant qu'elle n'est pas affectée à l'utilisation de la méthode Hello#hello=
Synthétisée.
Un autre problème important, comme décrit kch, est que vous devez être conscient des différents contextes actifs lors de la déclaration d'une classe. Lors de la déclaration d'une classe, le récepteur par défaut (self) dans la portée la plus externe sera l'objet qui représente la classe elle-même. Par conséquent, votre code créera d'abord une variable d'instance de classe lors de l'affectation à @hello
Au niveau de la classe.
Les méthodes internes self seront l'objet sur lequel la méthode est invoquée, donc vous essayez d'imprimer la valeur d'une variable d'instance avec le nom @hello
Dans l'objet, qui n'existe pas (notez qu'il est parfaitement légal de lire une variable d'instance non existante).
Vous devez ajouter une méthode initialize
:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
La première @hello
dans votre code s'appelle une variable d'instance de classe.
Il s'agit d'une variable d'instance de l'objet classe vers laquelle pointe la constante Hello
. (et qui est une instance de la classe Class
.)
Techniquement, lorsque vous êtes dans l'étendue class
, votre self
est défini sur l'objet de votre classe actuelle et @variables
se rapportent à votre self
actuel. Je suis nul à expliquer ces choses.
Vous pouvez obtenir tout cela et bien plus de détails pour vous en regardant cette collection de screencasts de 5 $ chacun des programmeurs pragmatiques .
(Ou vous pouvez demander des clarifications ici et je vais essayer de mettre à jour.)
il y a une description claire dans le livre "Le langage de programmation Ruby"), lisez-le sera très utile. Je le colle ici (à partir du chapitre 7.1.16):
Une variable d'instance utilisée dans une définition de classe mais en dehors d'une définition de méthode d'instance est un variable d'instance de classe.
class Point
# Initialize our class instance variables in the class definition itself
@n = 0 # How many points have been created
@totalX = 0 # The sum of all X coordinates
@totalY = 0 # The sum of all Y coordinates
def initialize(x,y) # Initialize method
@x,@y = x, y # Sets initial values for instance variables
end
def self.new(x,y) # Class method to create new Point objects
# Use the class instance variables in this class method to collect data
@n += 1 # Keep track of how many Points have been created
@totalX += x # Add these coordinates to the totals
@totalY += y
super # Invoke the real definition of new to create a Point
# More about super later in the chapter
end
# A class method to report the data we collected
def self.report
# Here we use the class instance variables in a class method
puts "Number of points created: #@n"
puts "Average X coordinate: #{@totalX.to_f/@n}"
puts "Average Y coordinate: #{@totalY.to_f/@n}"
end
end
......
Parce que les variables d'instance de classe ne sont que des variables d'instance d'objets de classe, nous pouvons utiliser attr, attr_reader et attr_accessor pour créer des méthodes d'accesseur pour elles.
class << self
attr_accessor :n, :totalX, :totalY
end
Avec ces accesseurs définis, nous pouvons faire référence à nos données brutes comme Point.n, Point.totalX et Point.totalY.
J'avais oublié qu'il y avait un concept de "variable d'instance de classe" dans Ruby. Dans tous les cas, le problème du PO semblait déroutant, et n'était pas vraiment abordé dans aucune des réponses jusqu'ici, à l'exception d'un indice dans la réponse de kch: c'est un problème de portée. (Ajouté sur edit: En fait, la réponse de sris aborde ce point à la fin, mais je laisserai cette réponse de toute façon, comme je pense que l'exemple le code peut être utile pour comprendre le problème.)
Dans une classe Ruby, un nom de variable commençant par @
peut faire référence à l'une des deux variables: soit à une variable d'instance soit à une variable d'instance de classe, selon l'endroit dans la classe auquel il est fait référence. C'est un gotcha assez subtil.
Un exemple clarifiera le point. Voici un petit Ruby classe de test (tout le code testé en irb):
class T
@@class_variable = "BBQ"
@class_instance_variable_1 = "WTF"
@class_instance_variable_2 = "LOL"
def self.class_method
puts "@@class_variable == #{@@class_variable || 'nil'}"
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
def initialize
@instance_variable = "omg"
# The following line does not assign a value to the class instance variable,
# but actually declares an instance variable withthe same name!
@class_instance_variable_1 = "wtf"
puts "@@class_variable == #{@@class_variable || 'nil'}"
# The following two lines do not refer to the class instance variables,
# but to the instance variables with the same names.
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
def instance_method
puts "@@class_variable == #{@@class_variable || 'nil'}"
# The following two lines do not refer to the class instance variables,
# but to the instance variables with the same names.
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
end
J'ai nommé les variables selon ce que je pensais qu'elles étaient, bien que cela ne soit pas toujours le cas:
irb> T.class_method
@@class_variable == BBQ
@class_instance_variable_1 == WTF # the value of the class instance variable
@class_instance_variable_2 == LOL # the value of the class instance variable
@instance_variable == nil # does not exist in the class scope
=> nil
irb> t = T.new
@@class_variable == BBQ
@class_instance_variable_1 == wtf # the value of the instance variable
@class_instance_variable_2 == nil # the value of the instance variable
@instance_variable == omg
=> #<T:0x000000015059f0 @instance_variable="omg", @class_instance_variable_1="wtf">
irb> t.instance_method
@@class_variable == BBQ
@class_instance_variable_1 == wtf # the value of the instance variable
@class_instance_variable_2 == nil # the value of the instance variable
@instance_variable == omg
=> nil
irb> T.class_method
@@class_variable == BBQ
@class_instance_variable_1 == WTF # the value of the class instance variable
@class_instance_variable_2 == LOL # the value of the class instance variable
@instance_variable == nil # does not exist in the class scope
=> nil
Le @@class_variable
et @instance_variable
se comportent toujours comme prévu: la première est définie au niveau de la classe et, qu'elle soit référencée dans une méthode de classe ou dans une méthode d'instance, elle contient la valeur qui lui est affectée en haut. Ce dernier n'obtient qu'une valeur dans un objet de classe T
, donc dans une méthode de classe, il fait référence à une variable inconnue dont la valeur est nil
.
La méthode de classe nommée avec imagination class_method
affiche les valeurs de @@class_variable
et les deux @class_instance_variable
s comme prévu, c'est-à-dire initialisé en haut de la classe. Cependant, dans les méthodes d'instance initialize
et instance_method
, différentes variables du même nom sont accessibles, c'est-à-dire , variables d'instance, pas les variables d'instance de classe.
Vous pouvez voir que l'affectation dans la méthode initialize
n'a pas affecté la variable d'instance de classe @class_instance_variable_1
, car l'appel ultérieur de class_method
sort son ancienne valeur, "WTF"
. Au lieu de cela, la méthode initialize
a déclaré une nouvelle variable d'instance, celle qui est également nommée (de manière trompeuse) @class_instance_variable_1
. La valeur qui lui est attribuée, "wtf"
, est généré par les méthodes initialize
et instance_method
.
La variable @class_instance_variable_2
dans l'exemple de code est équivalent à la variable @hello
dans le problème d'origine: il est déclaré et initialisé en tant que variable d'instance de classe, mais lorsqu'une méthode d'instance fait référence à une variable de ce nom, elle voit en fait ne variable d'instance du même nom - un qui n'a jamais été déclaré, sa valeur est donc nulle.
Je recommanderais également de regarder les variables de classe qui sont préfixées avec "@@" - voici un exemple de code pour vous montrer comment les variables de classe et d'instance sont différentes:
class Vars
@@classvar="foo"
def test
@instancevar="bar"
end
def Vars.show
puts "classvar: #{@@classvar}"
puts "instancevar: #{@instancevar}"
end
def instance_show
puts "classvar: #{@@classvar}"
puts "instancevar: #{@instancevar}"
end
end
# only shows classvar since we don't have an instance created
Vars::show
# create a class instance
vars = Vars.new
# instancevar still doesn't show b/c it hasn't been initialized
vars.instance_show
# initialize instancevar
vars.test
# now instancevar shows up as we expect
vars.instance_show