Klassen- und Klassenhierarchievariablen in Ruby

Sucht man nach einem Klassenvariablenkonzept für Ruby trifft man über kurz oder lang auf das @@-Konzept. Wer sich darauf einlässt kann schnell Probleme bekommen. Der Grund ist relativ einfach: @@-Variablen sind keine Klassenvariablen.

Einfache „Klassenvariablen“

class Cat
  
  @@size = :small
  
  def self.size
    @@size
  end

  def self.size= new_size
    @@size = new_size
  end

  def size
    @@size
  end

end

expect(Cat.size).to be :small
expect(Cat.new.size).to be :small
Cat.size = :medium
expect(Cat.size).to be :medium
expect(Cat.new.size).to be :medium

Vererbung

class HouseCat < Cat
end

class Lion < Cat
end
expect(Lion.size).to be :small
Lion.size = :big
expect(Lion.size).to be :big
expect(HouseCat.size).to be :big

Hauskatze und Löwe teilen die selbe Variable, was zu erwarten war. Lässt sich das trennen?

@@-Variablen sind keine Klassenvariablen

class HouseCat < Cat
end

class Lion < Cat
  @@size = :big
end
expect(Lion.size).to be :big
expect(HouseCat.size).to be :big # !!!

Nein. Die gesetzte Variable gilt für die gesamte Klassenhierarchie aufwärts.

Superklassen haben übrigens keinen Zugriff:

class Cat
  
  def self.size
    @@size
  end

end

class Lion < Cat
  @@size = :big
end
expect{ Lion.size }.to raise_error NameError

Richtige Klassenvariablen

Da in Ruby auch Klassen Objekte sind, sind Instanzvariablen dieser Objekte auch Klassenvariablen. Bemerkenswert ist hier die strenge Sichtbarkeitsbeschränkung: Selbst wenn @small und self.size in der selben Klassen definiert werden, kann self.size nach der Vererbung in die HouseCat nicht länger mit @small auf die Klassenvariable von Cat zurgreifen.

class Cat
  
  @size = :small
  
  def self.size
    @size
  end

end

class HouseCat < Cat
end

class Lion < Cat
  @size = :big
end
expect(Lion.size).to be :big
expect(Cat.size).to be :small
expect(HouseCat.size).to be_nil

Aber hier können ja wieder Klassenhierarchievariablen helfen.