Variável de instância da classe Ruby vs. variável de classe

179

Eu li " Quando as variáveis ​​de instância do Ruby são configuradas? ", Mas tenho duas ideias para usar variáveis ​​de instância de classe.

As variáveis ​​de classe são compartilhadas por todos os objetos de uma classe; as variáveis ​​de instância pertencem a um objeto. Não há muito espaço para usar variáveis ​​de instância de classe se tivermos variáveis ​​de classe.

Alguém poderia explicar a diferença entre esses dois e quando usá-los?

Aqui está um exemplo 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

Eu entendo agora, variáveis ​​de instância de classe não são passadas ao longo da cadeia de herança!

Elmor
fonte

Respostas:

276

Variável de instância em uma classe:

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]

Variável de classe:

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]

Com uma variável de instância em uma classe (não em uma instância dessa classe), você pode armazenar algo comum a essa classe sem que as subclasses também as obtenham automaticamente (e vice-versa). Com as variáveis ​​de classe, você tem a conveniência de não precisar escrever self.classde um objeto de instância e (quando desejável) também obtém compartilhamento automático em toda a hierarquia de classes.


Mesclando-os em um único exemplo que também abrange variáveis ​​de instância em instâncias:

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

E então em ação:

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] 
Phrogz
fonte
@Phronz Qual é a diferença entre self.things e self.class.things que você mencionou no código?
cyborg
1
@cyborg self.thingsreferenciou um método thingsno escopo atual (no caso de uma instância de uma classe, será o método da instância), onde faz self.class.thingsreferência a um thingsmétodo da classe de escopo atual (novamente no caso de uma instância de uma classe, isso significaria o método de classe).
graffzon
Linda explicação.
aliahme922 12/06
30

Eu acredito que o principal (apenas?) Diferente é a herança:

class T < S
end

p T.k
=> 23

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

p T.s
=> nil

As variáveis ​​de classe são compartilhadas por todas as "instâncias de classe" (ou seja, subclasses), enquanto as variáveis ​​de instância de classe são específicas apenas para essa classe. Mas se você nunca pretende estender sua classe, a diferença é puramente acadêmica.

bioneuralnet
fonte
1
Essa não é a única diferença. A instância "compartilhada" vs "vai além da herança. Se você colocar getters de instância, obterá S.new.s => nile S.new.k => 23.
Andre Figueiredo
27

Fonte

Disponibilidade para métodos de instância

  • Variáveis ​​de instância de classe estão disponíveis apenas para métodos de classe e não para métodos de instância.
  • Variáveis ​​de classe estão disponíveis para métodos de instância e métodos de classe.

Herança

  • Variáveis ​​de instância de classe são perdidas na cadeia de herança.
  • Variáveis ​​de classe não são.
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
Sachin Saxena
fonte
15

Como outros disseram, as variáveis ​​de classe são compartilhadas entre uma determinada classe e suas subclasses. As variáveis ​​de instância de classe pertencem a exatamente uma classe; suas subclasses são separadas.

Por que esse comportamento existe? Bem, tudo em Ruby é um objeto - até classes. Isso significa que cada classe tem um objeto da classe Class(ou melhor, uma subclasse de Class) correspondente a ela. (Quando você diz class Foo, está realmente declarando uma constante Fooe atribuindo um objeto de classe a ela.) E todo objeto Ruby pode ter variáveis ​​de instância, portanto, objetos de classe também podem ter variáveis ​​de instância.

O problema é que variáveis ​​de instância em objetos de classe realmente não se comportam da maneira que você normalmente deseja que as variáveis ​​de classe se comportem. Você geralmente deseja que uma variável de classe definida em uma superclasse seja compartilhada com suas subclasses, mas não é assim que as variáveis ​​de instância funcionam - a subclasse possui seu próprio objeto de classe e esse objeto de classe possui suas próprias variáveis ​​de instância. Então eles introduziram variáveis ​​de classe separadas com o comportamento que você provavelmente deseja.

Em outras palavras, variáveis ​​de instância de classe são como um acidente do design de Ruby. Você provavelmente não deve usá-los, a menos que saiba especificamente o que está procurando.

Brent Royal-Gordon
fonte
então variável de classe é como variável estática em Java?
Kick Buttowski #
3

Perguntas frequentes oficiais sobre Ruby: Qual é a diferença entre variáveis ​​de classe e variáveis ​​de instância de classe?

A principal diferença é o comportamento referente à herança: as variáveis ​​de classe são compartilhadas entre uma classe e todas as suas subclasses, enquanto as variáveis ​​de instância de classe pertencem apenas a uma classe específica.

As variáveis ​​de classe, de alguma forma, podem ser vistas como variáveis ​​globais no contexto de uma hierarquia de herança, com todos os problemas que acompanham as variáveis ​​globais. Por exemplo, uma variável de classe pode (acidentalmente) ser reatribuída por qualquer uma de suas subclasses, afetando todas as outras classes:

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" (!)

Ou então, uma classe ancestral pode ser reaberta e alterada posteriormente, com efeitos possivelmente surpreendentes:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

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

class Object
  @@var = "object"
end

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

Portanto, a menos que você saiba exatamente o que está fazendo e precise explicitamente desse tipo de comportamento, é melhor usar variáveis ​​de instância de classe.

notapatch
fonte
2

Para aqueles com experiência em C ++, talvez você esteja interessado em uma comparação com o equivalente em 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, ké uma staticvariável similar. Isso é 100% como uma variável global, exceto pelo fato de pertencer à classe (com o escopo definido como correto). Isso facilita evitar confrontos entre variáveis ​​nomeadas de maneira semelhante. Como qualquer variável global, há apenas uma instância dessa variável e a modificação é sempre visível por todos.

Por outro lado, sé um valor específico do objeto. Cada objeto tem sua própria instância do valor. No C ++, você deve criar uma instância para ter acesso a essa variável. No Ruby, a própria definição de classe é uma instância da classe (em JavaScript, isso é chamado de protótipo); portanto, você pode acessar a spartir da classe sem instanciação adicional. A instância da classe pode ser modificada, mas a modificação de sserá específica para cada instância (cada objeto do tipo S). Portanto, modificar um não mudará o valor em outro.

Alexis Wilke
fonte
1

Embora possa imediatamente parecer útil utilizar variáveis ​​de instância de classe, como as variáveis ​​de instância de classe são compartilhadas entre subclasses e podem ser referidas nos métodos singleton e de instância, há uma desvantagem significativa. Eles são compartilhados e, portanto, as subclasses podem alterar o valor da variável de instância da classe e a classe base também será afetada pela alteração, que geralmente é um comportamento indesejável:

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" 

O Rails apresenta um método útil chamado class_attribute. Como o nome indica, ele declara um atributo em nível de classe cujo valor é herdável por subclasses. O valor class_attribute pode ser acessado nos métodos singleton e de instância, como é o caso da variável de instância de classe. No entanto, o grande benefício com class_attribute no Rails é que as subclasses podem mudar seu próprio valor e não afetam a classe pai.

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

 C.c
 => "c" 

class D < C
end

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

 C.c
 => "c" 
Donato
fonte
Boa ligação, eu não tinha usado isso antes. Parece funcionar, mas você precisa ter certeza de acrescentar self.sempre que quiser acessar o atributo c, por exemplo self.c. Os documentos dizem que um default:parâmetro pode ser passado, class_attributemas parece não funcionar devido ao ponto que acabei de mencionar self.
Dex
Quando você diz "Embora possa parecer imediatamente útil utilizar variáveis ​​de instância de classe", acho que você quer dizer "variáveis ​​de classe", não "variáveis ​​de instância de classe, certo?" (Veja ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett
Sim, esta resposta confunde completamente "variáveis ​​de instância de classe" e "variáveis ​​de classe", que é o ponto principal da questão.
stevo 11/06