Software Patterns: Immutable Object

Ein Software Muster zum Implementieren unveraenderlicher Objekte

Published:

Disclaimer: This blog post is written in German. I may add an English version later.

Inhalt

Übersicht

  • Name: Immutable Object (dt. Unveränderliches Objekt)
  • Typ: Implementation Pattern (dt. Implementierungsmuster)
  • Quellen:
    • Bloch, Joshua. Effective Java. Addison-Wesley, 2001.
    • Evans, Eric. Domain-Driven Design (DDD). Addison-Wesley, 2003.

Einleitung

Ein Immutable Object ist ein Objekt, dessen Zustand nicht mehr verändert werden kann, nachdem es erzeugt wurde. Im Vergleich dazu kann ein Mutable Object verändert werden, nachdem es erzeugt wurde.

Joshua Bloch gibt in seinem Buch Effective Java folgende Empfehlung:

Classes should be immutable unless there’s a very good reason to make them mutable. […] If a class cannot be made immutable, limit its mutability as much as possible.

Vorteile

Ein Immutable Object:

  • ist einfach zu konstruieren, zu testen und zu benutzen.
  • ist threadsicher (PHP: irrelevant; Java: relevant).
  • benötigt keinen Kopierkonstruktor und keine Implementierung einer Methode zum Klonen (PHP: magische Methode / Interzeptormethode __clone; Java: Methode java.lang.Object.clone).
  • muss nicht defensiv kopiert werden, wenn es als Feld verwendet wird.
  • kann einfach in Collections verwendet werden.
  • vermeidet Invarianz.
  • bedingt, dass Klasseninvarianten ausschließlich beim Erzeugen eines Objekts erfasst werden müssen.

Nachteile

Da die Werte eine Immutable Object unveränderlich sind, besteht die Gefahr, dass eine große Anzahl von Objekten erzeugt wird. Dies wird insbesondere dann zum Problem, wenn das Erstellen neuer Objekte kostenintensiv ist (z.B. viel Ein-/Ausgabe benötigt). Dieses Problem kann aber mit dem Builder (dt. Erbauer) Erzeugungsmuster gelöst werden.

Implementierung

Vorgehen

  1. Deklarieren der Klasse als final. Dies stellt sicher, dass die Klasse nicht überschrieben werden bzw. keine Unterklasse gebildet werden kann.
  2. Deklarieren aller Felder der Klasse mit der Sichtbarkeit private (in Java zusätzlich mit dem Modifizierer final).
  3. Implementieren eines Complete Constructor (dt. Vollständiger Konstruktor). Alternativ kann auch eine Creational Method (dt. Erzeugungsmethode) implementiert werden.
  4. Vermeiden aller Methoden, die den Zustand des Objekts in irgend einer Art ändern. Dies schließt nicht nur Mutator-Methoden (“Setter”-Methoden) mit ein.
  5. Defensives Kopieren von veränderbaren Feldern der Klasse beim Austausch mit dem Aufrufer. Dies schließt sowohl Eingabe- als auch Ausgabeparameter mit ein.

Beispiele

Im folgenden Beispiel wird eine simples Immutable Object implementiert. Die zu implementierende Klasse soll einen Planeten (sehr) vereinfacht modellieren. Ein Planet soll über einen Namen, eine Masse sowie ein Entdeckungs-Datum verfügen.

PHP 5.4

Man beachte die Kommentare (in englischer Sprache) innerhalb der folgenden Quelltexte.

Folgendes Bespiel demonstriert, wie man ein Immutable Object falsch implementiert. Die Kommentare neben den echo-Aufrufen geben das Ergebnis bereits preis. Das Problem sind (wie so oft) Referenzen.

Das folgende Beispiel benutzt die wiederverwendbare PHP Komponente FlorianWolters\Component\Immutable um ein Immutable Object korrekt zu implementieren. Die Komponente implementiert das Immutable Object Muster mit Hilfe eine Traits, einer Exception-Klasse und eines Marker Interfaces.

In PHP 5.4 gibt es zwei Fallstricke beim Implementieren des Immutable Object Musters:

  1. Der Schreibzugriff auf nicht deklarierte Eigenschaften eines Objekts ist standardmäßig möglich:

    $obj = new \stdClass;
    $obj->value = 'foo';
    

    Aus diesem Grund wird beim Aufruf der magischen Methode __set eine Exception vom Typ FlorianWolters\Component\Core\ImmutableException geworfen.

  2. Es ist möglich die magische Methode __construct (der Konstruktor) auf einem Objekt aufzurufen (ein weiterer WTF-Moment):

    $obj = new \stdClass;
    $obj->__construct();
    

    Für ein Immutable Object muss sichergestellt werden, dass der Konstruktor nur einmal aufgerufen werden kann. Dies wird über den Aufruf der Methode throwImmutableExceptionIfConstructed() des Traits FlorianWolters\Component\Core\ImmutableTrait sichergestellt, die bei einem erneuten Aufruf des Konstruktors eine FlorianWolters\Component\Core\ImmutableException wirft. Dabei ist zu beachten, dass der Aufruf der Methode throwImmutableExceptionIfConstructed() die erste Aktion innerhalb des Konstruktors sein muss.

    Hinweis: Ich suche noch nach einer simpleren Methode, die weniger fehleranfällig ist.

Java

Das obige Beispiel in der Skriptsprache PHP wurde von folgender Webseite portiert: Java Practices -> Immutable objects

Die Seite enthält Ausführungen (in englischer Sprache) zur Implementierung des Immutable Object Musters in der Programmiersprache Java.

Verwandte Muster

  • Value Object (dt. Wertobjekt)
  • Immutable Interface (dt. Unveränderliches Interface)
  • Immutable Adapter (dt. Unveränderlicher Adapter)
  • Complete Constructor (dt. Vollständiger Konstruktor)
  • Creational Method (dt. Erzeugungsmethode)
  • Builder (dt. Erbauer)
  • Flyweight (dt. Fliegengewicht)