Variabile di istanza di classe Ruby vs. variabile di classe


179

Ho letto " Quando vengono impostate le variabili di istanza di Ruby? ", Ma ho due idee su quando usare le variabili di istanza di classe.

Le variabili di classe sono condivise da tutti gli oggetti di una classe, le variabili di istanza appartengono a un oggetto. Non c'è molto spazio per usare le variabili di istanza di classe se abbiamo variabili di classe.

Qualcuno potrebbe spiegare la differenza tra questi due e quando usarli?

Ecco un esempio di codice:

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

Ora capisco, le variabili di istanza di classe non vengono passate lungo la catena di ereditarietà!

Risposte:


276

Variabile di istanza su una 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]

Variabile di 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]

Con una variabile di istanza su una classe (non su un'istanza di quella classe) è possibile memorizzare qualcosa di comune a quella classe senza che anche le sottoclassi le ottengano automaticamente (e viceversa). Con le variabili di classe, si ha la comodità di non dover scrivere self.classda un oggetto di istanza e (quando desiderato) si ottiene anche la condivisione automatica in tutta la gerarchia di classi.


Unendo questi insieme in un singolo esempio che copre anche le variabili di istanza su istanze:

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 poi in azione:

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 Qual è la differenza tra self.things e self.class.things che hai citato nel codice?
cyborg,

1
@cyborg ha fatto self.thingsriferimento a un metodo thingsnell'ambito corrente (nel caso di un'istanza di una classe, sarà il metodo dell'istanza), dove fa self.class.thingsriferimento a un thingsmetodo dalla classe dell'ambito corrente (sempre nel caso di un'istanza di una classe significherebbe il metodo di classe).
graffzon,

Bella spiegazione
aliahme922,

30

Credo che il principale (solo?) Diverso sia l'ereditarietà:

class T < S
end

p T.k
=> 23

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

p T.s
=> nil

Le variabili di classe sono condivise da tutte le "istanze di classe" (cioè sottoclassi), mentre le variabili di istanza di classe sono specifiche solo per quella classe. Ma se non hai mai intenzione di estendere la tua classe, la differenza è puramente accademica.


1
Questa non è l'unica differenza. La "condivisione" vs "istanza" va oltre la semplice eredità. Se metti getter di istanze otterrai S.new.s => nile S.new.k => 23.
Andre Figueiredo,

27

fonte

Disponibilità ai metodi di istanza

  • Le variabili dell'istanza di classe sono disponibili solo per i metodi di classe e non per i metodi di istanza.
  • Le variabili di classe sono disponibili sia per i metodi di istanza che per i metodi di classe.

inheritability

  • Le variabili dell'istanza di classe vengono perse nella catena di ereditarietà.
  • Le variabili di classe non lo sono.
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

Come altri hanno detto, le variabili di classe sono condivise tra una determinata classe e le sue sottoclassi. Le variabili dell'istanza di classe appartengono esattamente a una classe; le sue sottoclassi sono separate.

Perché esiste questo comportamento? Bene, tutto in Ruby è un oggetto, persino le classi. Ciò significa che ogni classe ha un oggetto della classe Class(o meglio, una sottoclasse di Class) corrispondente ad essa. (Quando dici class Foo, stai davvero dichiarando una costanteFoo e assegnandogli un oggetto di classe.) E ogni oggetto Ruby può avere variabili di istanza, quindi anche gli oggetti di classe possono avere variabili di istanza.

Il problema è che le variabili di istanza sugli oggetti di classe non si comportano davvero nel modo in cui normalmente si vogliono comportare. Di solito si desidera che una variabile di classe definita in una superclasse sia condivisa con le sue sottoclassi, ma non è così che funzionano le variabili di istanza: la sottoclasse ha il proprio oggetto di classe e quell'oggetto di classe ha le sue variabili di istanza. Quindi hanno introdotto variabili di classe separate con il comportamento che è più probabile che tu voglia.

In altre parole, le variabili di istanza di classe sono una specie di incidente del design di Ruby. Probabilmente non dovresti usarli a meno che tu non sappia specificamente che sono quello che stai cercando.


quindi la variabile di classe è come una variabile statica in Java?
Calcia Buttowski il

3

Domande frequenti su Ruby ufficiali: qual è la differenza tra variabili di classe e variabili di istanza di classe?

La differenza principale è il comportamento relativo all'ereditarietà: le variabili di classe sono condivise tra una classe e tutte le sue sottoclassi, mentre le variabili di istanza di classe appartengono solo a una classe specifica.

Le variabili di classe in qualche modo possono essere viste come variabili globali nel contesto di una gerarchia di ereditarietà, con tutti i problemi che derivano dalle variabili globali. Ad esempio, una variabile di classe potrebbe (accidentalmente) essere riassegnata da una qualsiasi delle sue sottoclassi, interessando tutte le altre classi:

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

Oppure, una classe di antenati potrebbe essere successivamente riaperta e modificata, con effetti forse sorprendenti:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

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

class Object
  @@var = "object"
end

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

Quindi, a meno che tu non sappia esattamente cosa stai facendo e non abbia esplicitamente bisogno di questo tipo di comportamento, è meglio usare variabili di istanza di classe.


2

Per quelli con uno sfondo C ++, potresti essere interessato a un confronto con l'equivalente 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";

Come possiamo vedere, kè una staticvariabile simile. Questo è il 100% come una variabile globale, solo che è di proprietà della classe ( con ambito di essere corretto). Ciò rende più semplice evitare scontri tra variabili con nomi simili. Come ogni variabile globale, esiste solo un'istanza di quella variabile e modificarla è sempre visibile a tutti.

D'altra parte, sè un valore specifico dell'oggetto. Ogni oggetto ha la sua istanza del valore. In C ++, è necessario creare un'istanza per avere accesso a quella variabile. In Ruby, la definizione della classe è essa stessa un'istanza della classe (in JavaScript, questo si chiama prototipo), quindi è possibile accedere salla classe senza un'istanza aggiuntiva. L'istanza della classe può essere modificata, ma la modifica di ssarà specifica per ogni istanza (ogni oggetto di tipo S). Quindi la modifica di uno non cambierà il valore in un altro.


1

Mentre può sembrare immediatamente utile utilizzare le variabili di istanza di classe, poiché le variabili di istanza di classe sono condivise tra sottoclassi e possono essere citate sia nei metodi singleton che in quelli di istanza, c'è un singolare svantaggio. Sono condivisi e quindi le sottoclassi possono cambiare il valore della variabile di istanza della classe e anche la classe base sarà influenzata dalla modifica, che di solito è un comportamento indesiderato:

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 introduce un pratico metodo chiamato class_attribute. Come suggerisce il nome, dichiara un attributo a livello di classe il cui valore è ereditabile dalle sottoclassi. È possibile accedere al valore class_attribute in entrambi i metodi singleton e di istanza, come nel caso della variabile di istanza di classe. Tuttavia, l'enorme vantaggio di class_attribute in Rails è che le sottoclassi possono cambiare il proprio valore e non influiranno sulla classe genitore.

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

 C.c
 => "c" 

class D < C
end

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

 C.c
 => "c" 

Bella telefonata, non l'avevo mai usato prima. Sembra funzionare anche se è necessario essere sicuri di anteporre self.ogni volta che si desidera accedere all'attributo c, ad es self.c. I documenti dicono che un default:parametro può essere passato class_attributema non sembra funzionare a causa del punto appena menzionato self.
Dex,

Quando dici "Anche se può sembrare immediatamente utile utilizzare le variabili di istanza di classe", penso che intendi "variabili di classe", non "variabili di istanza di classe, giusto?" (Vedi ruby-lang.org/it/documentation/faq/8/. )
Keith Bennett il

Sì, questa risposta confonde completamente le "variabili di istanza di classe" e le "variabili di classe", che rappresentano l'intero punto della domanda.
stevo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.