Zellteilung

Das Erzeugen von Objekten mit einem Konstruktor ist ja eine feine Sache, aber nicht die einzige Möglichkeit, an neue Objekte zu kommen. Unix hat es vorgemacht, schon lange bevor die Objektorientierung populär wurde.

In Pocket speichern vorlesen Druckansicht 20 Kommentare lesen
Lesezeit: 6 Min.
Von
  • Michael Wiedeking

Das Problem ist allgegenwärtig: Wie kommt man an Objekte, die beliebige (aber festgelegte) Eigenschaften haben? Die Lösung dazu ist eigentlich ganz einfach: Man beginnt mit einem einzigen Objekt. Dieses braucht nur minimale Eigenschaften, wobei eine davon die Fähigkeit des Klonens ist, also ein Objekt mit all seinen Methoden und Attributen – mit Ausnahme seiner Identität – kopiert wird.

So gibt es unter Unix den Init-Prozess, sozusagen den Urprozess. Er hat alle grundlegenden Eigenschaften, den ein Prozess unter Unix haben kann. Ruft man nun die Betriebssystemfunktion fork auf, wird dieser Prozess exakt kopiert – der komplette Speicher, sämtliche offenen Kanäle, eben alle Attribute. Es gibt aus Sicht der beiden Prozesse nur einen einzigen Unterschied: Dem Originalprozess liefert der Aufruf von fork die Prozessnummer der Kopie, während die Kopie einfach nur Null geliefert bekommt.

Genial einfach. Denn nun kann der Klon, wenn nötig, seine Eigenschaften ändern und unabhängig davon andere Wege gehen, losgelöst von seiner Verwandtschaft zum Original. Jetzt können Aufgaben parallel erledigt werden, wobei man über die geteilten Kanäle kommunizieren könnte. Oder aber der Klon überschreibt seinen Adressraum mit einem neuen Programm – ohne dabei auf die gemeinsamen Kanäle verzichten zu müssen.

Hier ist der Vorteil, dass der neue, eventuell zu überschreibende Prozess nur die Eigenschaften ändern muss, die sich vom Original unterscheiden sollen. So ist etwa der Prozessbesitzer in den meisten Fällen derjenige, der auch den Originalprozess gestartet hat, und deshalb kann dieser einfach übernommen (oder ererbt) werden. Die Alternative, das explizite Erzeugen von Prozessen, erfordert nämlich, dass immer alle Parameter angegeben werden müssen – auch die offensichtlichen.

Ergänzt man also bei Objekten die Möglichkeit, neue Methoden und Attribute hinzuzufügen, kann man mit dem Klonen alles erreichen, was man auch mit Klassen erreichen kann. Jedes Objekt kann so prototypisch wie eine Klasse verwendet werden, nur dass man statt

     new X 

einfach

     X.clone() 

schreibt. Auf diese Weise entsteht analog zu den Klassen eine Objekthierarchie, die wie bei den Klassen keine Wünsche offen lässt.

Das ist übrigens auch der Grund, warum es in der UML keine dedizierte Notation für den Konstruktor gibt. Das irritiert gelegentlich Anfänger, die mit an C++ angelehnte Sprachen wie C# oder Java groß werden, weil sie nicht verstehen, warum das Erzeugen von Objekten so vernachlässigt wird. Natürlich kann man diese Art der Erzeugung leicht mit einem Stereotyp darstellen, aber irgendwie ist es nicht dasselbe.

Apropos Anfänger: Ohnehin ist es nicht leicht zu verstehen, warum sich ein Konstruktor – wenn man denn einen hat – von anderen Methoden unterscheidet. In Ruby beispielsweise wird ein Objekt einer Klasse C mit

     C.new

erzeugt, was dann implizit die initialize-Methode aufruft, was eine (zugegebenermaßen nicht) ganz normale Methode ist. Gegebenenfalls werden dabei Parameter, die dem new übergeben wurden, an diese Initialisierungsmethode weitergereicht.

Die Schreibweise ist meiner Meinung nach (insbesondere im Zusammenhang mit den Möglichkeiten der Meta-Programmierung unter Ruby) viel gefälliger, da sie klar macht, dass es sich bei der Erzeugungsmethode zwar um eine spezifische Methode der Klasse handelt, sich diese aber von ihren Mechanismen her nicht von anderen Methoden unterscheidet. Und diese kann bei den Klassenobjekten natürlich überschrieben werden, denn andere Klassen liefern ja auch anders geartete Objekte. Die Klasse C ist dann auch optisch als ein Objekt ihrer Meta-Klasse (mit einer unveränderlichen Identität) zu erkennen.

(Wäre übrigens die Initialisierung einfach eine Methode, täte man sich in vielerlei Hinsicht leichter. Man müsste deutlich weniger erklären, etwa warum der Konstruktor nicht wie die Methoden ohne Rückgabewert void ist. Auch die Serialisierung würde sich vereinfachen. Werden nämlich Objekte deserialisiert, muss erst ein Objekt erzeugt werden. Dafür muss ein parameterloser Konstruktor vorhanden sein, der ja nicht notwendigerweise vom Entwickler bereitgestellt wird. Dann werden bereits initialisierte Werte überschrieben, was einer Katastrophe gleichkommt, wenn diese Elemente final oder const sind. Mit einer echten Methode wäre das nicht mehr ganz so problematisch, denn dann müsste für die Deserialisierung nur eine Methode bereitgestellt werden, die einen Eingabestrom zur Initialisierung nutzt. Darüber hinaus könnte eine solche Methode dann auch von einem Serializable-Interface gefordert werden!)

Ein weiterer Nachteil beim expliziten Erzeugen von Objekten ist der, dass dabei immer alle nötigen Parameter, womöglich in unterschiedlichen Zusammensetzungen, angegeben werden müssen. Deshalb bedient man sich gelegentlich des Builder-Patterns. Builder ist eine Hilfsklasse, deren einzige Aufgabe es ist, sämtliche Parameter für die Erzeugung eines Objektes zu sammeln.

   Person p = new PersonBuilder("Nachname")
.addFirstName("Vor")
.addAddress()
.addStreet("Weg").addNummer("123")
.addZipCode("00001").addCity("Hintertupf")
.addProfession(Profession.MEISTER)
.addThis("…")
.addThat("…")
.create();

Das hat den Vorteil, dass man einzelne Aufrufe weglassen kann (etwa addThis), die create-Methode eine Konsistenzprüfung machen kann und – was am bemerkenswertesten ist – auch Objekte anderer (natürlich kompatibler) Klassen zu liefern.

Falls jetzt der Einwurf kommen sollte, dass man dies auch mit Settern nach der Konstruktion eines Objekts machen könnte, muss dieser abgewiesen werden; denn möglicherweise sollen die Eigenschaften nach der Initialisierung nicht mehr änderbar sein. Oder noch schlimmer: Vielleicht müssen die Felder final oder const sein, was bezüglich eines Memory-Modells völlig andere Auswirkungen haben könnte.

All das bekommt man mit den Prototypen geschenkt. Und das wird auch der Grund sein, warum es relativ viele Programmiersprachen gibt, die diesen Mechanismus nutzen.

Schön ausprobieren lässt sich das übrigens mit JavaScript, das alle dafür benötigten Eigenschaften hat. Besonders interessant ist es dort, dass man Objekte einfrieren und damit weitere Veränderungen unterbinden kann. Mit JavaScript kann man übrigens noch ganz andere Dinge machen, die mit der neuen Meta-Ebene richtig spannend sind. Aber das ist – wie so oft – eine ganz andere Geschichte und wird ein anderes Mal erzählt werden. ()