Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Klassen und Objekte



Klassen und Objekte sind ganz offensichtlich von entscheidender Bedeutung für Ruby, aber auf den ersten Blick sieht das alles etwas verwirrend aus. Es scheint eine Reihe von Konzepten zu geben: Klassen, Objekte, Klassen-Objekte, Instanzen, Klassen-Methoden und auch noch Singleton-Klassen. In Wirklichkeit benutzt Ruby aber nur eine einzige grundlegende Klassen- und Objekt-Struktur, über die wir in diesem Kapitel reden werden. Tatsächlich ist das zugrunde liegende Modell so einfach, dass wir es in einem einzigen Absatz beschreiben können.

Ein Ruby-Objekt besitzt drei Komponenten: eine Reihe von Flags, ein paar Instanz-Variablen und eine dazugehörende Klasse. Eine Ruby-Klasse ist ein Objekt der Klasse Class, die alle diese Objekt-Dinger enthält, sowie zusätzlich eine Liste mit Methoden und eine Referenz auf eine Superklasse (die wiederum selber eine andere Klasse ist). Alle Methoden-Aufrufe in Ruby benennen einen Empfänger (der defaultmäßig self ist, das aktuelle Objekt). Ruby findet die zu startende Methode, indem es die Liste der Methoden der Empfänger-Klasse durchsucht. Findet es die Methode dort nicht, sucht es in der nächsten Superklasse und dann in deren Superklasse und so weiter. Wird die Methode nicht in der Empfänger-Klasse und auch nicht in deren Vorfahren gefunden, so ruft Ruby die Methode method_missing des ursprünglichen Empfängers auf.

Das war dann alles --- die ganze Erklärung. Weiter mit dem nächsten Kapitel.

``Moment noch,'' hören wir den Leser rufen, ``Ich habe doch gutes Geld für dieses Kapitel ausgegeben. Was ist denn mit diesem ganzen anderen Kram --- Singleton-Klassen, Klassen-Methoden und so was. Wie funktionieren die denn?'' [Wenn einer der Leser tatsächlich gutes Geld für dieses Buch ausgeben will: ich maile ihm gerne meine Kontonummer, der Übersetzer]

Gute Frage.

Wie Klassen und Objekte interagieren

Jede Klassen/Objekt-Interaktion kann man mit dem oben angegebenen einfachen Modell beschreiben: Objekte referenzieren auf Klassen und Klassen referenzieren auf null oder mehr Superklassen. Allerdings können die Details der Implementierung ein bisschen knifflig sein.

Um das alles zu zeigen, halten wir es für das Einfachste, die Objekte aufzuzeichnen, die Ruby tatsächlich im Speicher anlegt [Ein Bild sagt mehr als tausend Worte, der Übersetzer]. Auf den folgenden Seiten sehen wir uns also all die möglichen Kombinationen von Klassen und Objekten an. Beachte, dass das keine Klassen-Diagramme im UML-Sinne sind: wir zeigen Objekte im Speicher und die Zeiger zwischen ihnen.

Das Basis-Objekt für jeden Tag

Wir fangen mit einem aus einer einfachen Klasse gebildeten Objekt an. Figur 19.1 auf Seite 243 zeigt ein von der Variable lucille referenziertes Objekt, die Klasse Guitar dieses Objekts, und deren Oberklasse Object. Beachte, wie die Klassenreferenz des Objekts (klass genannt, aus historischen Gründen, die Andy wirklich nerven) auf das Klassen-Objekt zeigt und wie der super-Zeiger dieser Klasse auf die Eltern-Klasse referenziert.

Ein einfaches Objekt, seine Klasse und Oberklasse

Figur 19.1 Ein einfaches Objekt, seine Klasse und Oberklasse

Wenn wir die Methode lucille.play aufrufen, geht Ruby zum Empfänger, lucille, und folgt der klass-Referenz zum Klassen-Objekt Guitar. Dort durchsucht Ruby die Methodentabelle, findet play und ruft es auf. Wenn wir stattdessen lucille.display aufrufen, macht Ruby dasselbe, findet aber display nicht in der Methodentabelle der Klasse Guitar. Dann folgt es der super-Referenz zu Obejct, der Oberklasse von Guitar, wo es die Methode findet und ausführt.

Und was ist mit dem Meta?

Aufmerksame Leser (jawoll, das sind sie alle) werden bemerkt haben, dass die klass-Unterpunkte der Klassen-Objekte in Figur 19.1 nirgends hinzeigen. Mittlerweile haben wir alle Informationen, um uns vorzustellen, wo sie hinzeigen sollten. Wenn man lucille.play()schreibt, dann folgt Ruby dem klass-Zeiger von lucille, um das Klassen-Objekt zu finden, in dem es nach dieser Methode suchen soll. Was ist aber, wenn wir eine Klassen-Methode aufrufen, wie etwa Guitar.strings(...)? Dabei ist der Emnpfänger das Klassen-Objekt Guitar selber. Um also konsistent zu bleiben, müssen wir die Methoden in eine andere Klasse stecken, die vom klass-Zeiger von Guitar referenziert wird. Das wird dann Meta-Klasse genannt. Wir bezeichnen hier die Metaklasse von Guitar mit Guitar'. Aber das ist noch nicht alles. Weil Guitar eine Unterklasse von Object ist, muss auch seine Metaklasse Guitar' eine Unterklasse der Metaklasse von Object, nämlich Object' sein. In Figur 19.2 zeigen wir diese zusätzlichen Metaklassen.

Guitar mit Metaklasse

Figur 19.2 Guitar mit Metaklasse

Wenn Ruby Guitar.strings() ausführt, dann folgt es denselben Regeln wie vorher auch: es geht zum Empfänger, Klasse Guitar, folgt der klass-Referenz zur Klasse Guitar' und findet die Methode.

Beachte schließlich auch noch, dass sich ein ``S'' in die Flags der Klasse Guitar' eingeschlichen hat. Die von Ruby automatisch erzeugten Klassen werden intern als Singleton-Klassen markiert. Singleton-Klassen werden bei Ruby etwas anders behandelt. Von außen gesehen ist der offensichtlichste Unterschied, dass sie einfach unsichtbar sind: sie werden niemals zurückgeliefert von Methoden wie Module#ancestors oder ObjectSpace::each_object.

Objekt-spezifische Klassen

In Ruby kann man eine Klasse erzeugen, die an ein spezielles Objekt gebunden ist. Im folgenden Beispiel erzeugen wir zwei String-Objekte. Mit einem von ihnen verbinden wir dann eine anonyme Klasse und überschreiben dabei eine der Methoden der Basis-Klasse dieses Objekts und fügen eine weitere Methode hinzu.

a = "hello"
b = a.dup
class <<a
  def to_s
    "The value is '#{self}'"
  end
  def twoTimes
    self + self
  end
end
a.to_s » "The value is 'hello'"
a.twoTimes » "hellohello"
b.to_s » "hello"

Dieses Beispiel nutzt die ``class <<obj''-Notation, die im Wesentlichen sagt: ``baue eine neue Klasse nur für dieses obj-Objekt.'' Wir hätten das auch so schreiben können:

a = "hello"
b = a.dup
def a.to_s
  "The value is '#{self}'"
end
def a.twoTimes
  self + self
end
a.to_s » "The value is 'hello'"
a.twoTimes » "hellohello"
b.to_s » "hello"

Der Effekt ist in beiden Fällen derselbe: eine Klasse wird zum Objekt ``a'' hinzugefügt. Das gibt uns einen zeimlich genauen Hinweis darauf, wie das in Ruby implementiert ist: Eine Singleton-Klasse wird erzeugt und als a's direkte Klasse eingefügt. a's original Klasse, String, wird dabei zu der Oberklasse dieses Singletons. Die Vorher- Nachher-Bilder dazu werden in Figur 19.3 gezeigt.

Ruby führt mit diesen Singleton-Klassen noch eine kleine Optimierung durch. Wenn der klass-Zeiger eines Objekts bereits auf eine Singleton-Klasse zeigt, dann wird keine neue erzeugt. Das heißt für das vorherige Beispiel, dass die erste der beiden Methoden-Definitionen eine Singleton-Klasse erzeugt, die zweite fügt ihr einfach nur eine Methode hinzu.

Ein Objekt mit Singleton-Klasse

Figur 19.3 Ein Objekt mit Singleton-Klasse

Mixin-Module

Wenn eine Klasse ein Modul einbindet, dann werden die Instanz-Methoden dieses Moduls als Instanz-Methoden der Klasse verfügbar. Es ist fast so, als ob das Modul eine Oberklasse dieser Klasse wird. Und so funktioniert das auch. Wenn man ein Modul einbindet, erzeugt Ruby eine anonyme Proxy-Klasse, die das Modul referenziert, und fügt diese Proxy-Klasse als direkte Oberklasse der einbindenden Klasse ein. Die Proxy-Klasse enthält Referenzen auf die Instanz-Variablen und -Methoden des Moduls. Wichtig dabei ist: Das gleiche Modul kann in verschiedenen Klassen eingebunden werden und taucht dann in verschiedenen Vererbungs-Ketten auf. Trotzdem gibt es dank der Proxy-Klasse nur ein zu grunde liegendes Modul: Änderungen an einer Methoden-Definition in diesem Modul zeigen sich auch in allen Klassen, die dieses Modul schon früher eingebunden haben oder noch einbinden werden.

module SillyModule
  def hello
    "Hello."
  end
end
class SillyClass
  include SillyModule
end
s = SillyClass.new
s.hello » "Hello."

module SillyModule
  def hello
    "Hi, there!"
  end
end
s.hello » "Hi, there!"

Die Beziehung zwischen Klassen und den eingebungenen Modulen zeigt die Figur 19.4. Falls mehrere Module eingebunden werden, so werden sie der Kette in ihrer Reihenfolge hinzu gefügt.

Ein eingebautes Modul und seine Proxy-Klasse

Figur 19.4 Ein eingebautes Modul und seine Proxy-Klasse

Falls ein Modul selber wieder ein anderes Modul einbindet, so wird eine Kette von Proxy-Klassen zu jeder Klasse hinzu gefügt, die dieses Modul einbindet, ein Proxy pro direkt oder indirekt eingebundenes Modul.

Objekte erweitern

Genauso wie man eine anonyme Klasse für ein Objekt mit ``class <<obj'' erzeugen kann, kann man auch ein Modul zu einem Objekt hinzu mischen mit Object#extend. Beispiel:

module Humor
  def tickle
    "hee, hee!"
  end
end
a = "Grouchy"
a.extend Humor
a.tickle » "hee, hee!"

Man kann mit extend einen interessanten Trick durchführen. Wenn man es innerhalb einer Klassen-Definition benutzt, werden die Modul-Methoden zu Klassen-Methoden.

module Humor
  def tickle
    "hee, hee!"
  end
end
class Grouchy
  include Humor
  extend  Humor
end
Grouchy.tickle » "hee, hee!"
a = Grouchy.new
a.tickle » "hee, hee!"

Der Grund dafür ist, dass der Aufruf von extend äquivalent ist zu self.extend. Die Methode wird also zu self hinzu gefügt und das ist innerhalb einer Klassen-Definition eben die Klasse selber.

Klassen- und Modul-Definitionen

Nachdem wir nun alle Kombinationen von Klassen und Objekten ausgeschöpft haben, können wir endlich wieder zum Programmieren zurück kehren und uns die Grundlagen der Klassen- und Modul-Definitionen ansehen.

In Sprachen wie C++ und Java werden Klassen-Definitionen beim Kompilieren ausgewertet: Der Compiler lädt die Symbol-Tabellen, berechnet den benötigten Speicherplatz, konstruiert Dispath-Tabellen und macht all diese sonstigen unklaren Sachen, über die wir nicht so gerne nachdenken.

In Ruby ist das anders. In Ruby sind Klassen-Definitionen ausführbarer Code. Obowhl der auch beim Kompilieren geparsed wird, werden Klassen und Module erst zur Laufzeit erzeugt, wenn ihre Definition aufgerufen wird. (Dasselbe gilt auch für Methoden-Definitionen.) Damit kann man ein Programm wesentlich dynamischer strukturieren als in den meisten konventionellen Sprachen. Man kann Entscheidungen einmal treffen lassen, während die Klasse definiert wird, und muss nicht jedesmal nachprüfen, wenn die Klasse oder das Objekt benutzt wird. Die Klasse aus dem folgenden Beispiel entscheidet während ihrer Definition, welche Version einer Verschlüsselungs-Routine sie erzeugt.

class MediaPlayer
  include Tracing if $DEBUGGING

  if ::EXPORT_VERSION     def decrypt(stream)       raise "Decryption not available"     end   else     def decrypt(stream)       # ...     end   end

end

Wenn Klassen-Definitionen ausführbarer Code sind, müssen sie im Kontext irgendeines Objekts ablaufen: self muss irgendwohin zeigen. Sehen wir uns an, wohin.

class Test
  puts "Type of self = #{self.type}"
  puts "Name of self = #{self.name}"
end
erzeugt:
Type of self = Class
Name of self = Test

Das heißt, dass eine Klassen-Definition mit dieser Klasse als aktuellem Objekt ausgeführt wird. Wenn wir uns noch richtig an den Abschnitt über die Metaklassen auf Seite 242 erinnern, bedeutet dies, dass die Methoden aus der Metaklasse und deren Oberklassen während der Ausführung der Methoden-Definition zur Verfügung stehen. Das prüfen wir nach.

class Test
  def Test.sayHello
    puts "Hello from #{name}"
  end

  sayHello end
erzeugt:
Hello from Test

In diesem Beispiel definieren wir eine Klassen-Methode Test.sayHello und rufen sie im Rumpf der Klassen-Definition auf. Innerhalb von sayHello rufen wir name auf, eine Instanz-Methode der Klasse Module. Weil Module ein Vorfahr von Class ist, können seine Instanz-Methoden ohne expliziten Empfänger innerhalb einer Klassen-Definition aufgerufen werden.

Tatsächlich sind viele Anweisungen, die man während der Definition einer Klasse oder eines Moduls benutzt, etwa alias_method, attr und public ganz einfach nur Methoden aus der Klasse Module. Dies eröffnet uns einige interessante Möglichkeiten --- man kann die Funktionalität von Klassen- und Modul-Definitionen mit Ruby-Code erweitern. Schauen wir uns ein paar Beispiele an.

Als erstes Beispiel werden wir die Möglichkeit einer einfachen Dokumentation für Module und Klassen hinzufügen. Damit können wir einen String mit von uns geschriebenen Klassen und Modulen verbinden, auf den während des Programmablaufs zugegriffen werden kann. Wir benutzen eine einfache Syntax.

class Example
  doc "This is a sample documentation string"
  # .. rest of class
end

Wir wollen doc für jede Klasse und jedes Modul verfügbar machen, also muss daraus eine Instanz-Methode der Klasse Module werden.

class Module
  @@docs = Hash.new(nil)
  def doc(str)
    @@docs[self.name] = str
  end

  def Module::doc(aClass)     # If we're passed a class or module, convert to string     # ('<=' for classes checks for same class or subtype)     aClass = aClass.name if aClass.type <= Module     @@docs[aClass] || "No documentation for #{aClass}"   end end

class Example   doc "This is a sample documentation string"   # .. rest of class end

module Another   doc <<-edoc     And this is a documentation string     in a module   edoc   # rest of module end

puts Module::doc(Example) puts Module::doc("Another")
erzeugt:
This is a sample documentation string
      And this is a documentation string
      in a module

Das zweite Beispiel ist eine Performance-Steigerung, basierend auf dem date-Modul von Tadayoshi Funaba (Beschreibung ab Seite 443). Wir haben zu Beispiel eine Klasse, die einen Basiswert repräsentiert (in diesem Fall ein Datum). Diese Klasse besitzt nun viele Attribute, die denselben Wert auf unterschiedliche Weise darstellen: als Nummer des Julianischen Tages, als [Jahr, Monat, Tag]-Tripel und so weiter. Jeder Wert repräsentiert dasselbe Datum und benötigt eventuell eine recht komplexe Berechnung. Deshalb mochten wir jedes Attribut nur einmal berechnen, wenn das erste Mal darauf zugegriffen wird.

Nach üblicher Vorgehensweise macht man das mit einem Test für jede Zugriffs-Variable:

class ExampleDate
  def initialize(dayNumber)
    @dayNumber = dayNumber
  end

  def asDayNumber     @dayNumber   end

  def asString     unless @string       # complex calculation       @string = result     end     @string   end

  def asYMD     unless @ymd       # another calculation       @ymd = [ y, m, d ]     end     @ymd   end   # ... end

Das ist allerdings etwas klobig --- wir können das auch ein wenig schlanker machen.

Was uns vorschwebt ist eine Anweisung, mit der der Rumpf einer speziellen Methode nur einmal aufgerufen wird. Der Rückgabewert soll dabei gespeichert werden. Danach soll ein Aufruf derselben Methode nur noch den gespeicherten Wert zurück geben, ohne ihn noch mal neu zu berechnen. Das sieht dem once-Modifikator von Eiffel sehr ähnlich. Wir möchten etwas schreiben können, das etwa so aussieht:

class ExampleDate
  def asDayNumber
    @dayNumber
  end

  def asString     # complex calculation   end

  def asYMD     # another calculation     [ y, m, d ]   end

  once :asString, :asYMD end

Wir können once als Anweisung benutzen, indem wir es als Klassen-Methode von ExampleDate schreiben, aber wie sollte es intern aussehen? Der Trick ist, dass es die Methoden, deren Namen es übergeben bekommt, überschreibt. Für jede Methode erzeugt es einen Alias für den Original-Code und erzeugt dann eine neue Methode mit demselben Namen. Diese neue Methode macht zwei Dinge. Als erstes ruft sie die Original-Methode auf (über den Alias) und speichert den Rückgabewert in einer Instanz-Variablen. Als zweites defniniert sie sich selber um, so dass bei weiteren Afurufen nur noch der Wert der Instanz-Variablen zurück gegeben wird. Dies ist Tadayoshi Funabas Code, nur ein wenig reformatiert.

def ExampleDate.once(*ids)
  for id in ids
    module_eval <<-"end_eval"
      alias_method :__#{id.to_i}__, #{id.inspect}
      def #{id.id2name}(*args, &block)
        def self.#{id.id2name}(*args, &block)
          @__#{id.to_i}__
        end
        @__#{id.to_i}__ = __#{id.to_i}__(*args, &block)
      end
    end_eval
  end
end

Dieser Code benutzt module_eval, um einen Code-Block im Kontext des aufrufenden Moduls (bzw. hier der aufrufenden Klasse) auszuführen. Die Original-Methode wird umbenannt in __nnn__, wobei der nnn-Anteil die Integer-Repräsentation der Symbol-Id des Methoden-Namens ist. Der Code nutzt denselben Namen für die speichernde Instanz-Variable. Der Großteil des Codes ist eine Methode, die sich dynamisch redefiniert. Beachte, dass diese Redefinition die Tatsache nutzt, dass Methoden eingeschachtelte Singleton-Methoden enthalten dürfen, ein raffinierter Trick.

Wenn du diesern Code verstehst, bist du schon weit vorangeschritten auf dem Weg zu wahrer Meisterschaft in Ruby.

Wir können es aber noch weiter treiben. Sieh dir das date-Modul an, da sieht die Methode once noch etwas anders aus.

class Date
  class << self
    def once(*ids)
      # ...
    end
  end
  # ...
end

Das Interessante hier ist die innere Klassen-Definition, ``class << self''. Damit wird eine Klasse definiert basierend auf dem Objekt self, und self ist gerade das Klassen-Objekt für Date. Das Ergebnis? Jede Methode, die in der inneren Klassen-Definition geschrieben wird, ist automatisch eine Klassen-Methode von Date.

Diese once-Eigenschaft ist allgemein einsetzbar --- das sollte mit jeder Klasse funktionieren. Wenn man once zu einer privaten Methode der Klasse Module machen würde, wäre es in jeder Ruby-Klasse verfügbar.

Klassen-Namen sind Konstanten

Wir sagten, dass das Aufrufen einer Klassen-Methode dasselbe ist, wie das Senden einer Nachricht an das ClassObjekt selber. Wenn man etwa String.new("gumby") schreibt, sendet man die Nachricht new an das Objekt, das die Klasse String ist. Aber woher weiß Ruby das? Schließlich sollte der Empfänger einer Nachricht eine Objekt-Referenz sein, was bedeutet, dass es irgendwo eine Konstante namens ``String'' geben muss, die eine Referenz auf das String-Objekt enthält. [Das ist eine Konstante, keine Variable, schließlich fängt ``String'' mit einem Großbuchstaben an.] Und tatsächlich, genau das passiert. Alle eingebauten und alle selbst definierten Klassen besitzen eine dazu gehörende globale Konstante mit dem selben Namen wie die Klasse. Das ist sowohl einfach als auch raffiniert. Die Raffinesse kommt daher, dass es tatsächlich zwei Dinge namens (zum Beispiel) String im System gibt, zum einen die Konstante, die ein Objekt der Klasse String referenziert, und zum anderen das Objekt selber.

Die Tatsache, dass Klassen-Namen einfach nur Konstanten sind, bedeutet, dass man Klassen wie jedes andere Ruby-Objekt auch behandeln kann: man kann sie kopieren, sie an Methoden übergeben und sie in Ausdrücken benutzen.

def factory(klass, *args)
  klass.new(*args)
end
factory(String, "Hello") » "Hello"
factory(Dir,    ".") » #<Dir:0x4018d1a8>
flag = true
(flag ? Array : Hash)[1, 2, 3, 4] » [1, 2, 3, 4]
flag = false
(flag ? Array : Hash)[1, 2, 3, 4] » {1=>2, 3=>4}

Die Umgebung der Top-Level-Ausführung

Schon oft haben wir in diesem Buch behauptet, dass alles in Ruby ein Objekt ist. Da gibt es aber etwas, das wir immer und immer wieder benutzt haben und das dem zu widersprechen scheint --- die Umgebung der Top-Level-Ausführung.

puts "Hello, World"

Nirgendwo ein Objekt in Sicht. Wir könnten genauso gut in einer Variante von Fortran oder QW-Basic schreiben. Wenn man aber etwas tiefer schürft, stößt man doch auf Objekte und Klassen, die selbst im einfachsten Code noch lauern.

Wir wissen, dass das Literal "Hello, World" einen Ruby-String erzeugt, da ist also schon mal ein Objekt. Wir wissen auch, dass der nicht ergänzte Methoden-Aufruf puts tatsächlich das selbe ist wie self.puts. Aber was ist nun ``self''?

self.type » Object

Auf dem obersten Level führen wir den Code im Kontext eines vordefinierten Objekts aus. Wenn wir Methoden definieren, erzeugen wir tatsächlich (private) Singleton-Methoden für dieses Objekt. Instanz-Variablen gehören auch zu diesem Objekt. Und weil wir im Kontext von Object sind, können wir alle Methoden von Object benutzen (inklusive die eingemischten von Kernel) in der Form wie eine Funktion. Das erklärt auch, warum wir Kernel-Methoden wie puts im Top-Level (und natürlich auch sonst überall) aufrufen können: diese Methoden sind Teil jeden Objekts.

Vererbung und Sichtbarkeit

Einen letzten Kniff gibt es noch bei der Klassen-Vererbung und der ist ziemlich verworren.

Innerhalb einer Klassen-Definition kann man die Sichtbarkeit einer Methode in der Klasse eines Vorfahren ändern. Zum Beispiel kann man dies machen:

class Base
  def aMethod
    puts "Got here"
  end
  private :aMethod
end

class Derived1 < Base   public :aMethod end

class Derived2 < Base end

In diesem Beispiel kann man aMethod in Instanzen der Klasse Derived1 aufrufen, aber nicht über Instanzen von Base oder Derived2.

Wie bringt Ruby nun dieses Kunststück fertig, dass eine Methode zwei verschiedene Sichtbarkeiten zeigt? Um es einfach zu sagen, Ruby mogelt.

Wenn eine Unterklasse die Sichtbarkeit einer Methode in einem Elternteil ändert, fügt Ruby in Wirklichkeit eine versteckte Proxy-Methode in der Unterklasse ein, die die Original-Methode mit super aufruft. Die Sichtbarkeit dieses Proxy wird dann auf die gewünschte Art gesetzt. Das bedeutet, dass der Code:

class Derived1 < Base
  public :aMethod
end

in Wriklichkeit derselbe ist wie:

class Derived1 < Base
  def aMethod(*args)
    super
  end
  public :aMethod
end

Mit dem Aufruf von super kann auf die Eltern-Methode unabhängig von deren Sichtbarkeit zugegriffen werden, so dass diese Umschreibung es der Unterklasse erlaubt, die Sichtbarkeitsregeln der Elternklasse zu überschreiben. Ganz schön schaurig, nicht wahr?

Objekte einfrieren

Manchmal hat man sehr hart gearbeitet, bis ein Objekt endlich das tut, was es soll, und es soll verdammt nochmal niemand wagen, es zu ändern. Manchmal möchte man so etwas wie ein undurchsichtiges Objekt zwischen zwei eigenen Klassen über eine Fremd-Klasse verschicken und es soll unverändert ankommen. Manchmal benutzt man ein Objekt als Hash-Schlüssel und möchte sicher sein, dass niemand es verändert. Manchmal verändert etwas eigene Objekte und Ruby soll eine Exception auslösen, sobald das passiert.

In Ruby gibt es dafür einen einfachen Mechanismus. Jedes Objekt kann eingefroren werden mit Object#freeze. Ein eingefrorenes Objekt kann nicht verändert werden: man kann seine Instanz-Variablen nicht ändern (weder direkt noch indirekt), man kann keine Singleton-Methoden hinzufügen, und im Falle einer Klasse oder eines Moduls kann man keine Methoden hinzufügen, entfernen oder ändern. Einmal eingefroren gibt es kein Zurück: es gibt kein Object#thaw. Man kann den Zustand eines Objekt prüfen mit Object#frozen?.

Was passiert, wenn man ein eingefrorenes Objekt kopiert? Das kommt auf die benutzte Methode an. Wenn man die clone-Methode eines Objekts benutzt, wird der gesamte Objekt-Zustand (inklusive ob es eingefroren ist) in das neue Objekt kopiert. Andererseits kopiert dup üblicherweise nur den Inhalt eines Objekts --- das neue Objekt erbt nicht den eingefrorenen Zustand.

str1 = "hello"
str1.freeze » "hello"
str1.frozen? » true
str2 = str1.clone
str2.frozen? » true
str3 = str1.dup
str3.frozen? » false

Auch wenn das Einfrieren von Objekten anfangs eine tolle Idee zu sein scheint, sollte man sich damit zurückhalten, bis man es wirklich braucht. Das Einfrieren ist eine dieser Ideen, die auf dem Papier ganz wesentlich aussehen aber in der Praxis nur selten benutzt werden.


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Jürgen Katins
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".