Variable de instancia de clase Ruby vs. variable de clase


179

Leí "¿ Cuándo se configuran las variables de instancia de Ruby? ", Pero no sé cuándo usar las variables de instancia de clase.

Las variables de clase son compartidas por todos los objetos de una clase, las variables de instancia pertenecen a un objeto. No queda mucho espacio para usar variables de instancia de clase si tenemos variables de clase.

¿Alguien podría explicar la diferencia entre estos dos y cuándo usarlos?

Aquí hay un ejemplo de código:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

Ahora entiendo, ¡las variables de instancia de clase no se pasan a lo largo de la cadena de herencia!

Respuestas:


276

Variable de instancia en una clase:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

Variable de clase:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

Con una variable de instancia en una clase (no en una instancia de esa clase) puede almacenar algo común a esa clase sin que las subclases también las obtengan automáticamente (y viceversa). Con las variables de clase, tiene la conveniencia de no tener que escribir self.classdesde un objeto de instancia y (cuando lo desee) también obtiene el uso compartido automático en toda la jerarquía de clases.


Fusionando estos en un solo ejemplo que también cubre variables de instancia en instancias:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

Y luego en acción:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 

@Phronz ¿Cuál es la diferencia entre self.things y self.class.things que mencionaste en el código?
cyborg

1
@cyborg hizo self.thingsreferencia a un método thingsen el alcance actual (en el caso de una instancia de una clase, será el método de la instancia), donde hace self.class.thingsreferencia a un thingsmétodo de la clase del alcance actual (nuevamente en el caso de una instancia de una clase, significaría El método de clase).
graffzon

Hermosa explicación
aliahme922

30

Creo que la principal (¿solo?) Diferente es la herencia:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

Las variables de clase son compartidas por todas las "instancias de clase" (es decir, subclases), mientras que las variables de instancia de clase son específicas solo de esa clase. Pero si nunca tiene la intención de extender su clase, la diferencia es puramente académica.


1
Esa no es la única diferencia. La "instancia compartida" versus "instancia" va más allá de la herencia. Si pones getters de instancia obtendrás S.new.s => nily S.new.k => 23.
Andre Figueiredo

27

Fuente

Disponibilidad para métodos de instancia

  • Las variables de instancia de clase están disponibles solo para métodos de clase y no para métodos de instancia.
  • Las variables de clase están disponibles tanto para los métodos de instancia como para los métodos de clase.

Heredabilidad

  • Las variables de instancia de clase se pierden en la cadena de herencia.
  • Las variables de clase no lo son.
class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method

15

Como otros dijeron, las variables de clase se comparten entre una clase dada y sus subclases. Las variables de instancia de clase pertenecen exactamente a una clase; Sus subclases están separadas.

¿Por qué existe este comportamiento? Bueno, todo en Ruby es un objeto, incluso las clases. Eso significa que cada clase tiene un objeto de la clase Class(o más bien, una subclase de Class) correspondiente a ella. (Cuando dices class Foo, realmente estás declarando una constanteFoo y asignándole un objeto de clase). Y cada objeto Ruby puede tener variables de instancia, por lo que los objetos de clase también pueden tener variables de instancia.

El problema es que las variables de instancia en los objetos de clase realmente no se comportan de la forma en que generalmente quieres que se comporten las variables de clase. Por lo general, desea que una variable de clase definida en una superclase se comparta con sus subclases, pero no es así como funcionan las variables de instancia: la subclase tiene su propio objeto de clase y ese objeto de clase tiene sus propias variables de instancia. Entonces, introdujeron variables de clase separadas con el comportamiento que es más probable que desee.

En otras palabras, las variables de instancia de clase son una especie de accidente del diseño de Ruby. Probablemente no debería usarlos a menos que sepa específicamente que son lo que está buscando.


Entonces, ¿la variable de clase es como una variable estática en Java?
Kick Buttowski

3

Preguntas frecuentes oficiales sobre Ruby: ¿Cuál es la diferencia entre las variables de clase y las variables de instancia de clase?

La principal diferencia es el comportamiento con respecto a la herencia: las variables de clase se comparten entre una clase y todas sus subclases, mientras que las variables de instancia de clase solo pertenecen a una clase específica.

Las variables de clase de alguna manera pueden verse como variables globales dentro del contexto de una jerarquía de herencia, con todos los problemas que vienen con las variables globales. Por ejemplo, una variable de clase podría (accidentalmente) ser reasignada por cualquiera de sus subclases, afectando a todas las demás clases:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

O bien, una clase de antepasado podría reabrirse y cambiarse más tarde, con efectos posiblemente sorprendentes:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

Entonces, a menos que sepa exactamente lo que está haciendo y necesite explícitamente este tipo de comportamiento, es mejor que use variables de instancia de clase.


2

Para aquellos con antecedentes en C ++, puede estar interesado en una comparación con el equivalente de C ++:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

Como podemos ver, kes una staticvariable similar. Esto es 100% como una variable global, excepto que es propiedad de la clase (con el alcance para ser correcto). Esto hace que sea más fácil evitar enfrentamientos entre variables con nombres similares. Al igual que cualquier variable global, solo hay una instancia de esa variable y modificarla siempre es visible para todos.

Por otro lado, ses un valor específico del objeto. Cada objeto tiene su propia instancia del valor. En C ++, debe crear una instancia para tener acceso a esa variable. En Ruby, la definición de clase es en sí misma una instancia de la clase (en JavaScript, esto se llama prototipo), por lo tanto, puede acceder sdesde la clase sin instanciación adicional. La instancia de clase se puede modificar, pero la modificación de sva a ser específica para cada instancia (cada objeto de tipo S). Por lo tanto, modificar uno no cambiará el valor en otro.


1

Si bien puede parecer inmediatamente útil utilizar variables de instancia de clase, dado que las variables de instancia de clase se comparten entre las subclases y se puede hacer referencia a ellas dentro de los métodos de instancia única y, existe un inconveniente singular. Se comparten y, por lo tanto, las subclases pueden cambiar el valor de la variable de instancia de clase, y la clase base también se verá afectada por el cambio, que generalmente es un comportamiento indeseable:

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Rails presenta un método útil llamado class_attribute. Como su nombre lo indica, declara un atributo de nivel de clase cuyo valor es heredable por subclases. Se puede acceder al valor de class_attribute en métodos singleton y de instancia, como es el caso con la variable de instancia de clase. Sin embargo, el gran beneficio de class_attribute en Rails es que las subclases pueden cambiar su propio valor y no afectarán a la clase principal.

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 

Buena decisión, no había usado esto antes. Parece funcionar, aunque debe asegurarse de anteponer self.cada vez que desee acceder al atributo c, por ejemplo self.c. Los documentos dicen default:que se puede pasar un parámetro, class_attributepero no parece funcionar debido al punto que acabo de mencionar self.
Dex

Cuando dices "Si bien puede parecer inmediatamente útil utilizar variables de instancia de clase", creo que te refieres a "variables de clase", no a "variables de instancia de clase ¿verdad?" (Ver ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett

Sí, esta respuesta confunde completamente "variables de instancia de clase" y "variables de clase", que es el punto central de la pregunta.
stevo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.