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
    1
    
    __clone
    ; Java: Methode
    1
    
    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
    1
    
    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
    1
    
    private
    (in Java zusätzlich mit dem Modifizierer
    1
    
    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

1
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:

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

    Aus diesem Grund wird beim Aufruf der magischen Methode

    1
    
    __set
    eine Exception vom Typ
    1
    
    FlorianWolters\Component\Core\ImmutableException
    geworfen.

  2. Es ist möglich die magische Methode

    1
    
    __construct
    (der Konstruktor) auf einem Objekt aufzurufen (ein weiterer WTF-Moment):

    1
    2
    
    $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

    1
    
    throwImmutableExceptionIfConstructed()
    des Traits
    1
    
    FlorianWolters\Component\Core\ImmutableTrait
    sichergestellt, die bei einem erneuten Aufruf des Konstruktors eine
    1
    
    FlorianWolters\Component\Core\ImmutableException
    wirft. Dabei ist zu beachten, dass der Aufruf der Methode
    1
    
    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)