Das Decorator Design Pattern

Studienprojekt von Philipp Hauer. 2009 - 2010. ©

Inhalt

Einführung

Gegeben sei folgendes Szenario: Ein Restaurant möchte seine beiden beliebtesten Gerichte "Wiener Schnitzel mit Pommes" und "Wiener Schnitzel mit Bratkartoffeln" modellieren. Der Preis und die Beschreibung sollen via Methoden abgefragt werden (Beispiel in Anlehnung an [VKBF], Seite 80ff.).

Unser Praktikant schlägt eine schnelle Lösung vor: Die zwei Gerichte werden abgebildet, indem für jedes Gericht eine eigene Klasse erstellt wird, die das Gerichtinterface implementiert und dessen Methoden getPreis() und druckeBeschreibung() jeweils ausprogrammiert.

Decorator Design Pattern

Soweit so gut. Doch wie so oft in der Softwareentwicklung ändern sich die Anforderungen an unsere Modellierung: Das Restaurant möchte nun alle (!) Basisgerichte (Tofu, Garnelen, Hüftsteak, Wiener Schnitzel) mit allen möglichen Beilagen (Nudeln, Pommes, Bratkartoffeln, Salat, Suppe) modellieren. Bei unserer jetzigen Modellierung würde dies zu einer Klassenexplosion führen. 4 Basisgerichte * 5 Beilagen = 20 Klassen/Gerichte.

Decorator Design Pattern

Die Nachteile sind offensichtlich und absolut inakzeptabel:

  • Wartungs- und Erweiterungsarbeiten erfordern enormen Zeitaufwand und sind zudem fehleranfällig. Was ist, wenn eine neue Beilage hinzukommt? 4 neue Klassen müssen erstellt werden. Was, wenn ein Basisgericht teurer wird? Der bestehende Code von 5 Klassen muss angepasst werden. Dass es dabei schnell zu Fehlern und damit Inkonsistenzen kommt, ist wahrscheinlich.
  • Mangelnde Flexibilität. Was ist, wenn ein Gast die doppelte Portion Bratkartoffeln möchte? Oder Suppe und Salat? Diese Fälle deckt unsere Modellierung nicht ab.
  • Mangelnde Dynamik. Es besteht keine Möglichkeit, Änderungen oder Modifikationen zur Laufzeit durchzuführen. Also Beilagen dynamisch hinzuzufügen und auch wieder zu entfernen.

Doch bevor wir uns die Lösung anschauen, holen wir etwas weiter aus. Dazu sei hier ein wichtiges OO-Entwurfsprinzip rezitiert:

Offen/Geschlossen-Prinzip (Open/Closed):
Entwürfe sollten für Erweiterungen offen, aber für Veränderungen geschlossen sein.

Gemeint ist, dass Erweiterungen (neue Verhalten etc.) ohne Änderungen an bestehenden Code in das System integriert werden können. Dies im Hinterkopf behaltend erinnern wir uns an ein weiteres stets aktuelles OO-Prinzip:

 Identifiziere jene Aspekte, die sich ändern und trenne sie von jenen, die konstant bleiben.

Was ist konstant? Jedes Basisgericht und jede Beilage für sich.
Was ist variabel? Die Kombination von Basisgericht und Beilage.

Warum also nicht für jedes Basisgericht und für jede Beilage eine einzelne Klasse schreiben und diese dann dynamisch miteinander kombinieren?

Decorator Design Pattern

Es zeichnet sich ab, dass das Offen/Geschlossen-Prinzip passt: Unsere Basisgericht- und Beilagenklassen sollen konstant sein und damit für Veränderung geschlossen, allerdings kann jedes Basisgericht durch diverse Beilagen erweitert werden und ist somit für Erweiterung offen. Das war jetzt sehr theoretisch. Wie realisieren wir dies?

Dazu begreifen wir Beilagen als "Wrapper", als Hülle, die wir um ein Basisgericht legen. Es sind Objekte, die ein Gericht (beispielweise eine Basisgericht) besitzen. also eine Instanzvariabale auf ein solches besitzen. Soll eine Salatbeilage seinen Preis ausgeben, so fragt er zuerst sein Hüftsteak nach dessen Preis und addiert anschließend seinen eigenen Salatpreis hinzu.

getPreis() von Salat delegiert die Preisberechnung an sein (Basis)Gericht und fügt seinen eigenen Preis dazu:

//Salatcode 
private Gericht gericht; 

public double getPreis() { 
    return gericht.getPreis() + 2.25; 
} 

Auch soll es möglich sein, Beilagen beliebig ineinander zu schachteln (Hüftsteak mit Nudeln und Salat). Folgendes Schema zur Visualisierung dieses Vorgangs:

Decorator Design Pattern

Der Trick, den wir nun nutzen, ist, dass unsere Beilagen vom selben Typ sind, wie die Basisgerichte: Gericht. Logisch: ein Basisgericht und eine Beilage zusammen sind immer noch ein Gericht. Beilagen erweitern somit ebenso das Interface Gericht. Damit ist es möglich, dass eine Beilage sowohl ein Basisgericht als auch eine andere Beilage einpacken kann (diese wiederum ein anderes Gericht...).

Decorator Design Pattern

Damit wäre das Decorator Pattern prinzipiell schon realisiert. Nur ist es üblich und zweckmäßig zwischen dem Interface Gericht und den konkreten Beilagen noch eine abstrakte Beilagenklasse zu schalten, die generischen Code für alle Beilagen enthalten kann. In unserem Fall wäre dies die Deklaration der Instanzvariable auf ein Gericht, sowie Methoden zum Setzen dieser Instanzvariable (Setter oder Konstruktor).

Decorator Design Pattern

Quellcode Gerichtinterface:

public interface Gericht { 
    public double getPreis(); 
    public void druckeBeschreibung(); 
}  	
Quellcode konkrete Basisgerichte:

class Hueftsteak implements Gericht { 
    public void druckeBeschreibung() { 
        System.out.print("Hüftsteak"); 
    } 
    public double getPreis() { 
        return 13.0; 
    } 
} 


class Tofu implements Gericht { 
    public void druckeBeschreibung() { 
        System.out.print("Tofu"); 
    } 
    public double getPreis() { 
        return 8.50; 
    } 
} 


class Garnelen implements Gericht { 
    public void druckeBeschreibung() { 
        System.out.print("Garnelen"); 
    } 
    public double getPreis() { 
        return 13.50; 
    } 

} 


class WienerSchnitzel implements Gericht { 
    public void druckeBeschreibung() { 
        System.out.print("WienerSchnitzel"); 
    } 
    public double getPreis() { 
        return 10.50; 
    } 
} 			
Quellcode abstrakte Beilage:

public abstract class Beilage implements Gericht { 
    protected Gericht gericht; 

    public Beilage(Gericht gericht) { 
        this.gericht = gericht; 
    } 
}  			
Quellcode konkrete Beilagen:

class Pommes extends Beilage { 
    public Pommes(Gericht gericht) { 
        super(gericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Pommes"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 2.50; 
    } 
} 


class Salat extends Beilage { 
    public Salat(Gericht gericht) { 
        super(gericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Salat"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 2.25; 
    } 
} 


class Nudeln extends Beilage { 
    public Nudeln(Gericht gericht) { 
        super(gericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Nudeln"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 4.50; 
    } 
} 


class Suppe extends Beilage { 
    public Suppe(Gericht gericht) { 
        super(gericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Suppe"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 1.50; 
    } 
} 


class Bratkartoffeln extends Beilage { 
    public Bratkartoffeln(Gericht gericht) { 
        super(gericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Bratkartoffeln"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 1.50; 
    } 
}  			

Nun können wir unsere Basisgerichte beliebig in Beilagen einpacken:

Beispielclient:

public static void main(String[] args) { 
    Gericht gericht = new Salat(new Nudeln(new Hueftsteak())); 
    gericht.druckeBeschreibung(); 
    //Hüftsteak, Nudeln, Salat 
    System.out.println(" für "+gericht.getPreis() + " Euro"); 
    // für 19.75 Euro 

    gericht = new Suppe(gericht); 
    gericht.druckeBeschreibung(); 
    //Hüftsteak, Nudeln, Salat, Suppe 
    System.out.println(" für "+gericht.getPreis() + " Euro"); 
    // für 21.25 Euro 
} 

Fassen wir die Vorteile unseres neuen Designs zusammen:

  • Flexibilität und Dynamik. Wir können unsere Basisgerichte nun (mehrfach und doppelt!) mit verschiedenen Beilagen umhüllen und damit ihr Verhalten flexibel und dynamisch (also auch zur Laufzeit) modifizieren.
  • Erweiterbarkeit und Robustheit. Wir haben ein System geschaffen, dass geschlossen für Veränderungen, aber offen für Erweiterungen ist. Das bedeutet, dass neue Beilage/Basisgerichte ohne Codeänderungen bestehender Basisgerichte und Beilagen ins System integriert werden können. Die Realisierung des Offen/Geschlossen-Prinzips erhöht die Wiederverwendbarkeit und Flexibilität unseres Designs.
  • Konsistenz und Wartbarkeit. Wir vermeiden im Vergleich zur statischen Vererbungslösung Coderedundanzen. So müssen wir das Verhalten eines jeden Gerichts (Preis, Beschreibung) nur einmal implementieren. Damit sinkt auch die Anfälligkeit für Fehler und Inkonsistenzen. Wartungs- und Erweiterungsarbeiten sind schneller durchgeführt, da nur eine Klasse angepasst werden.

Es zeigt sich, dass durch den Einsatz des Decorator Patterns, ein hohes Maß an Flexibilität und Dynamik gewonnen wird, während zeitgleich die Wartung erleichtert wird und Erweiterungen schnell und unkompliziert möglich sind.

Nach dieser Einführung wird im folgenden Abschnitt das Decorator Design Pattern formalisiert, näher analysiert und diskutiert.

Das Gerichtbeispiel mit Decorator Pattern Termini

Klasse Decorator Teilnehmer
Basisgerichte (Hüftsteak, Wiener Schnitzel etc.) Components (dt. Komponente)
Beilagen (Nudeln, Salat, Suppe etc.) Decorators (dt. Dekorierer)

Analyse und Diskussion

Gang Of Four-Definition

Decorator:
"Erweitere ein Objekt dynamisch um Zuständigkeiten. Dekorierer bieten eine flexible Alternative zur Unterklassenbildung, um die Funktionalität einer Klasse zu erweitern."
([GoF], Seite 199)

Beschreibung

Decorator Design Pattern

Das Decorator Design Pattern ermöglicht das dynamische Hinzufügen von Fähigkeiten zu einer Klasse. Dazu wird die Klasse, dessen Verhalten wir erweitern möchten (Component, Komponente), mit anderen Klassen (Decorator, Dekorierer) dekoriert (vgl. engl. "to wrap": umhüllen). Das heißt der Decorator umschließt (enthält) die Component. Der Decorator ist vom selben Typ wie das zudekorierende Objekt, hat somit die gleiche Schnittstelle und kann an der selben Stelle wie die Component benutzt werden. Er delegiert Methodenaufrufe an seine Component weiter und führt sein eigenes Verhalten davor oder danach aus.

Eine Component kann mit beliebig vielen Decorators dekoriert werden, um so seine Fähigkeiten immer weiter auszubauen.

Realisiert wird das Pattern beginnend mit einer abstrakten Superklasse (oder einem Interface) Component. Sowohl die konkreten Components als auch alle Decorators sind von diesem Typ. Component definiert weiterhin beliebig viele abstrakte Methoden, die alle konkreten Subklassen implementieren müssen (hier operate()). Die konkreten Components erweitern Component direkt und implementieren ihr Basisverhalten. Die abstrakte Klasse Decorator erweitert Component ebenfalls und hält zugleich eine Referenz auf ein (beliebiges) Objekt vom Typ Component. Konkrete Decorators erweitern schließlich Decorator (sind damit auch Components) und implementieren ihr Zusatzverhalten (operate()). Darin rufen sie zum einen die Methode(n) (operate()) der Component (geerbt von Decorator) auf und fügen davor oder danach ihr decoratorspezifisches Verhalten hinzu. Da es keine Rolle spielt, ob ihre Componentreferenz nun eine konkrete Component oder wiederum ein anderer Decorator ist, können Decorators beliebig geschachtelt werden.

Decorator Design Pattern

Component:

public abstract class Component { 
    //abstrakte Klasse oder Interface je nach Bedarf. 
    public abstract void operate(); 
} 			
ConcreteComponentA und ConcreteComponentB:

public class ConcreteComponentA extends Component { 
    public void operate() { 
        System.out.println("ConcreteComponentA operates."); 
    } 
} 

class ConcreteComponentB extends Component { 
    public void operate() { 
        System.out.println("ConcreteComponentB operates."); 
    } 
}  			
Decorator:

public abstract class Decorator extends Component { 
    protected Component component; 

    //Konstruktor zum komfortablen Initiieren am Client 
    public Decorator(Component component) { 
        this.component = component; 
    } 
    //operate() wird nicht implementiert. 
}  			
ConcreteDecorator1, ConcreteDecorator2 und ConcreteDecorator3:

class ConcreteDecorator1 extends Decorator { 
    public ConcreteDecorator1(Component component) { 
        super(component); 
    } 

    public void operate() { 
        //operate() der dekorierten Kompontente aufrufen vor 
        //oder nach der eigenen Funktionalitäst. 
        component.operate(); 
        //eigene Funktionalitäst: 
        System.out.println("ConcreteDecorator1 operates!"); 
    } 

} 

class ConcreteDecorator2 extends Decorator { 
    //ConcreteDecorator2 fügt der Komponente einen neuen Zustand hinzu. 
    private int newState; 

    public ConcreteDecorator2(Component component) { 
        super(component); 
        newState = 999; 
    } 

    public void operate() { 
        component.operate(); 
        System.out.println("ConcreteDecorator2 operates with a new State: " + newState); 
    } 
} 

class ConcreteDecorator3 extends Decorator { 
    public ConcreteDecorator3(Component component) { 
        super(component); 
    } 

    public void operate() { 
        component.operate(); 
        System.out.println("ConcreteDecorator3 operates with a new Operation: "+newOperation()); 
    } 

    //ConcreteDecorator3 fügt der Komponente eine neue Methode hinzu und 
    //erweitert seine Schnittstelle nach auÃüen. 
    public int newOperation() { 
        return (int)(Math.random() * 20); 
    } 
} 			
Beispielclient:

public static void main(String[] args) { 
    Component compA = new ConcreteComponentA(); 
    compA.operate(); 
    //ConcreteComponentA operates. 

    compA = new ConcreteDecorator1(compA); 
    compA.operate(); 
    //ConcreteComponentA operates. 
    //ConcreteDecorator1 operates! 

    Component compB = new ConcreteDecorator3(new ConcreteDecorator2(new ConcreteComponentB())); 
    compB.operate(); 
    //ConcreteComponentB operates. 
    //ConcreteDecorator2 operates with a new State: 999 
    //ConcreteDecorator3 operates with a new Operation: 12 
} 			

Anwendungsfälle

  • Zur dynamischen und transparenten Funktionserweiterung von Objekten.
    • Bei der Speicherung von Textdaten können zusätzliche Features, wie das Maskieren von Umlauten oder ein Komprimierungsalgorithmus hinzugeschaltet werden ([Sherzad]).
    • Eine Textkomponente kann mit einer Scollbar oder einem Rahmen dekoriert werden ([GoF], Seite 200). Siehe Anwendung in der Java-API.
  • Zum Hinzufügen, aber auch Entfernen von Funktionalitäten
    • Zu einer Anfrage setzbare Filter können nach dem Decorator Pattern modelliert und damit beliebig hinzugefügt oder entfernt werden ([PK], Seite 61).
  • Wenn Funktionalitätserweiterung mittels Vererbung impraktikabel ist:
    Dies ist zum einen der Fall bei voneinander unabhängigen Erweiterungen, wenn unter Beachtung jeder möglichen Erweiterungskombination eine schier unüberschaubare Anzahl von Klassen entstehen würde.
    • Das klassische Kaffeebeispiel: Es sind 4 Kaffeegrundsorten (Espresso, Edelröstung, Cappuccino, Latte Macchiato) und 4 optionale Zusätze (Milch, Schoko, Zucker, Sahne) gegeben. Das ergibt 16 Subklassen. Unüberschaubar, unwartbar und unflexibel ([VKBF], Seite 80ff).
    Zum anderen wenn die Klassendefinition versteckt oder nicht ableitbar (final) ist. Das Decorator Pattern kann damit eine Möglichkeit bieten, finale Klassen um Funktionalitäten zu erweitern.

Weitere anschauliche Beispiele:

  • Tuning verschiedener Autotypen mit verschiedenen Features (Tieferlegung, Spoiler, Chip) ([Langner]).
  • Gericht mit diversen Beilagen ([Pras]).
  • Verschiedene Telefontypen (Handy, klassisches Telefon) mit variablen Verhalten (Vibration, Klingeln, Lautlos, Leuchten) ([GruntzD]).

Vorteile

  • Dynamik und Flexibilität. Klassen können ohne statische Vererbung um Verhalten erweitert werden - und das sowohl zur Kompilier- als auch zur Laufzeit. Beliebige Kombinationen (auch Mehrfachkombinationen) von Decorators sind möglich. Sowohl Decorators als auch Components sind frei variierbar und wiederverwendbar. Weiterhin kann bei der Fähigkeitserweiterung durch Dekorieren die Schnittstelle der Component nach außen erweitert werden (vgl. BufferedReader mit readLine()).
  • Vermeidung von langen unübersichtlichen Vererbungshierarchien.
  • Transparenz. Einem Client, der mit einer Component arbeitet, kann eine dekorierte Component "untergeschoben" werden, ohne dass sein Code bricht. Der Client wird wiederverwendbar.
  • Performance. Man muss nur jene Funktionalitäten initialisieren (und die Kosten dafür tragen), die auch wirklich benötigt werden. Einmal initialisiert, steht die Funktionalität stets zur Verfügung. Dies ist ein großer Vorteil gegenüber einer komplexen Basisklasse, die alle möglichen Funktionen permanent bereit hält, Auswahl zwischen ihnen treffen muss und entsprechend Ressourcen verbraucht.
  • Wartbarkeit durch schlanke, kohäsive Klassen. Dank Decorator Pattern können überfrachtete unübersichtliche Basisklassen vermieden werden. Jeder Decorator repräsentiert genau eine Funktion und nichts anderes. Dadurch steigt die Klassenkohäsion und der entsprechende prägnante Code wird leichter wart- und erweiterbar.

Nachteile

  • Erschwerte Fehlerfindung. Fehler, die sich in den oft langen Aufrufketten von dekorierten Objekten verstecken, sind schwer zu finden.
  • Hohe Objektanzahl. Jedes zusätzliche Feature bedarf eines neuen Decoratorobjektes. Schnell kann die Anzahl der vielen kleinen, ähnlichen Objekten und ihr Initialisierungscode unübersichtlich werden. Eine Factory empfiehlt sich, um den Erstellungscode zu kapseln.
  • Unkomfortable, wortreiche API. Die entstandene Decorator-API, die der Client nutzen muss, um ein Objekt nach seinen Wünschen zu dekorieren, ist nicht einsteigerfreundlich, besonders, wenn das Know-How über das Decorator Pattern fehlt. Jeder, der die Programmiersprache Java gelernt hat und das erste Mal von der Java-IO-API samt unzähligen schachtelbaren Streams, Readern und Writern erschlagen wurde, kann dieses Problem nachvollziehen. Die Komplexität von Systemen steigt mit der Einführung des Decorator Patterns.
  • Keine Objektidentität zwischen Component und dekorierte Component. Obwohl der Client einen Decorator, der eine Component umhüllt, genauso behandelt wie eine undekorierte Component, handelt es sich dabei nicht um die gleichen Objekte. Clients die auf Objektidentität setzen, werden Fehler erzeugen.

Anwendung in der Java Standardbibliothek

In der Java API wird das Decorator Design Pattern an zahlreichen Stellen angewandt.

Swing GUI: Komponentenverschachtelung

An zahlreichen Stellen in GUI-Bibliotheken werden GUI-Komponenten dekoriert. Ein typisches Beispiel ist die JScrollPane von Swing.

Decorator Design Pattern JScrollPaneDecorator Design Pattern JScrollPane

Soll eine JTextArea oder ein JTree Scrollbalken erhalten, so muss man das Objekt nur einer JScrollPane als Konstruktorparameter übergeben und das JScollPane-Objekt stattdessen (es ist ja auch eine Component) auf den Container (JPanel/JFrame) hinzufügen. Soll nun die Component gezeichnet werden, so fügt das JScrollPane-Objekt dem JTextArea-Objekt das nötige Verhalten für Scrollbalken hinzu.

Nutzung des Decorators JScrollPane:

JTextArea textComponent = new JTextArea(12,20); 
JScrollPane scrollComponent = new JScrollPane(textComponent); 
add(scrollComponent); 
//oder add(new JScrollPane(new JTextArea(12,20)); 			

Java IO

Der berühmteste Nutzer des Decorator Design Patterns ist die Java IO. Hier sei stellvertretend die InputStream-Hierarchie vorgestellt. Bei OutputStream, Reader und Writer verhält es sich analog.

Decorator Design Pattern Java IO

Die konkreten Basisstreams (Concrete Components) können durch andere Streams beliebig dekoriert werden, um ihnen damit zusätzliche Funktionalität zuzufügen. So fügt ein BufferedInputStream dem Basisstream einen Buffer hinzu und ermöglicht damit performantes Lesen. Dieser kann wiederum weiteren Decoratorstreams übergeben werden bis das gewünschte Verhalten erreicht wurde.

Decorator Design Pattern IO

frei nach [VKBF], Seite 100

Interessant in der Java IO ist weiterhin der Decorator BufferedReader. Dieser Decorator implementiert nicht nur das Readerinterface (Componentinterface), sondern erweitert diese Schnittstelle noch um die Methode readLine(), mit der eine ganze Zeile statt einzelnen Zeichen gelesen werden kann. Um diese Methode nutzen zu können, muss der Client natürlich wissen, dass er es mit einem BufferedReader zu tun hat und nicht nur mit irgendeinen Reader.

Dekorierende Collections der Utilityklasse Collections

Auch die Utilityklasse Collections nutzt das Decorator Pattern. Genau genommen, wird das Pattern von deren static nested ("inneren") Klassen, wie SynchronizedList oder UnmodifiableList genutzt. Diese Klassen, die selber das Listinterface implementieren (und damit selber Lists sind), nehmen eine List als Konstrukturparameter und leiten die entsprechenden Listmethoden an ihre List weiter, nehmen dabei jedoch Modifikationen vor.

UnmodifiableList leitet nur lesende Zugriffe (get(), hashCode(), indexOf()) an seine dekorierte List weiter. Auf alle Versuche schreibend auf die Liste zuzugreifen (add(), remove(), addAll()) wird eine UnsupportedOperationException geworfen.

SynchronizedList synchronisiert alle Zugriffe auf die List.

Codeausschnitt der static nested Klasse UnmodifiableList:

//viel Code... 
static class UnmodifiableList<E> extends UnmodifiableCollection<E> implements List<E> { 

    final List<? extends E> list; 

    UnmodifiableList(List<? extends E> list) { 
        super(list); 
        this.list = list; 
    } 

    public int hashCode() { 
        return list.hashCode(); 
    } 
    public E get(int index) { 
        return list.get(index); 
    } 
    public E set(int index, E element) { 
        throw new UnsupportedOperationException(); 
    } 
    public void add(int index, E element) { 
        throw new UnsupportedOperationException(); 
    } 
    public E remove(int index) { 
        throw new UnsupportedOperationException(); 
    } 
    public int indexOf(Object o) { 
        return list.indexOf(o); 
    } 
    public int lastIndexOf(Object o) { 
        return list.lastIndexOf(o); 
    } 
    public boolean addAll(int index, Collection<? extends E> c) { 
        throw new UnsupportedOperationException(); 
    } 
    //viel Code... 
}  			
Codeausschnitt der static nested Klasse SynchronizedList:

//viel Code.... 
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> { 

    final List<E> list; 

    SynchronizedList(List<E> list) { 
        super(list); 
        this.list = list; 
    } 

    public boolean equals(Object o) { 
        synchronized (mutex) { 
            return list.equals(o); 
        } 
    } 
    public int hashCode() { 
        synchronized (mutex) { 
            return list.hashCode(); 
        } 
    } 
    public E get(int index) { 
        synchronized (mutex) { 
            return list.get(index); 
        } 
    } 
    public void add(int index, E element) { 
        synchronized (mutex) { 
            list.add(index, element); 
        } 
    } 
    public E remove(int index) { 
        synchronized (mutex) { 
            return list.remove(index); 
        } 
    } 
    public int indexOf(Object o) { 
        synchronized (mutex) { 
            return list.indexOf(o); 
        } 
    } 
    //viel Code... 
}