Prototypmuster in Java

1. Einleitung

In diesem Tutorial lernen wir eines der Creational Design Patterns kennen - das Prototype Pattern. Zuerst erklären wir dieses Muster und implementieren es dann in Java.

Wir werden auch einige seiner Vor- und Nachteile diskutieren.

2. Prototypmuster

Das Prototypmuster wird im Allgemeinen verwendet, wenn wir eine Instanz der Klasse (Prototyp) haben und neue Objekte erstellen möchten, indem wir nur den Prototyp kopieren .

Verwenden wir eine Analogie, um dieses Muster besser zu verstehen.

In einigen Spielen wollen wir Bäume oder Gebäude im Hintergrund. Möglicherweise stellen wir fest, dass wir nicht jedes Mal, wenn sich der Charakter bewegt, neue Bäume oder Gebäude erstellen und auf dem Bildschirm rendern müssen.

Also erstellen wir zuerst eine Instanz des Baums. Dann können wir aus dieser Instanz (Prototyp) so viele Bäume erstellen, wie wir möchten, und ihre Positionen aktualisieren. Wir können auch die Farbe der Bäume für ein neues Level im Spiel ändern.

Das Prototypmuster ist ziemlich ähnlich. Anstatt neue Objekte zu erstellen, müssen wir nur die prototypische Instanz klonen.

3. UML-Diagramm

Im Diagramm sehen wir, dass der Client den Prototyp anweist, sich selbst zu klonen und ein Objekt zu erstellen. Der Prototyp ist eine Schnittstelle und deklariert eine Methode zum Klonen. ConcretePrototype1 und ConcretePrototype2 implementieren die Operation, um sich selbst zu klonen.

4. Implementierung

Eine Möglichkeit, dieses Muster in Java zu implementieren, ist die Verwendung der clone () -Methode. Dazu würden wir die klonbare Schnittstelle implementieren .

Wenn wir versuchen zu klonen, sollten wir uns zwischen einer flachen oder einer tiefen Kopie entscheiden . Schließlich läuft es auf die Anforderungen hinaus.

Wenn die Klasse beispielsweise nur primitive und unveränderliche Felder enthält, verwenden wir möglicherweise eine flache Kopie.

Wenn es Verweise auf veränderbare Felder enthält, sollten wir eine tiefe Kopie erstellen. Wir könnten das mit Kopierkonstruktoren oder Serialisierung und Deserialisierung tun .

Nehmen wir das zuvor erwähnte Beispiel und sehen wir uns an, wie das Prototypmuster angewendet wird , ohne die klonbare Schnittstelle zu verwenden. Dazu erstellen wir eine abstrakte Klasse namens Tree mit der abstrakten Methode 'copy' .

public abstract class Tree { // ... public abstract Tree copy(); }

Nehmen wir nun an, wir haben zwei verschiedene Implementierungen von Tree namens PlasticTree und PineTree :

public class PlasticTree extends Tree { // ... @Override public Tree copy() { PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight()); plasticTreeClone.setPosition(this.getPosition()); return plasticTreeClone; } }
public class PineTree extends Tree { // ... @Override public Tree copy() { PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight()); pineTreeClone.setPosition(this.getPosition()); return pineTreeClone; } }

Hier sehen wir also, dass die Klassen, die Tree erweitern und die Kopiermethode implementieren , als Prototypen für die Erstellung einer Kopie von sich selbst fungieren können.

Mit dem Prototypmuster können wir auch Kopien von Objekten erstellen, ohne von den konkreten Klassen abhängig zu sein . Angenommen, wir haben eine Liste von Bäumen und möchten Kopien davon erstellen. Aufgrund des Polymorphismus können wir problemlos mehrere Kopien erstellen, ohne die Baumarten zu kennen.

5. Testen

Jetzt testen wir es:

public class TreePrototypesUnitTest { @Test public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() { // ... PlasticTree plasticTree = new PlasticTree(mass, height); plasticTree.setPosition(position); PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy(); anotherPlasticTree.setPosition(otherPosition); assertEquals(position, plasticTree.getPosition()); assertEquals(otherPosition, anotherPlasticTree.getPosition()); } }

Wir sehen, dass der Baum aus dem Prototyp geklont wurde und wir haben zwei verschiedene Instanzen von PlasticTree . Wir haben gerade die Position im Klon aktualisiert und die anderen Werte beibehalten.

Nun klonen wir eine Liste von Bäumen:

@Test public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() { // create instances of PlasticTree and PineTree List trees = Arrays.asList(plasticTree, pineTree); List treeClones = trees.stream().map(Tree::copy).collect(toList()); // ... assertEquals(height, plasticTreeClone.getHeight()); assertEquals(position, plasticTreeClone.getPosition()); }

Beachten Sie, dass wir hier eine tiefe Kopie der Liste erstellen können, ohne von den konkreten Implementierungen von Tree abhängig zu sein .

6. Vor- und Nachteile

Dieses Muster ist praktisch, wenn sich unser neues Objekt nur geringfügig von unserem vorhandenen unterscheidet. In einigen Fällen haben Instanzen möglicherweise nur wenige Statuskombinationen in einer Klasse. Anstatt neue Instanzen zu erstellen , können wir die Instanzen vorher mit dem entsprechenden Status erstellen und sie dann klonen, wann immer wir wollen .

Manchmal können wir auf Unterklassen stoßen, die sich nur in ihrem Zustand unterscheiden. Wir können diese Unterklassen eliminieren, indem wir Prototypen mit dem Anfangszustand erstellen und sie dann klonen.

Prototypmuster sollten wie jedes andere Entwurfsmuster nur verwendet werden, wenn dies angemessen ist. Da wir die Objekte klonen, kann der Prozess bei vielen Klassen komplex werden, was zu einem Durcheinander führt. Darüber hinaus ist es schwierig, Klassen mit Zirkelverweisen zu klonen.

7. Fazit

In diesem Tutorial haben wir die Schlüsselkonzepte des Prototypmusters kennengelernt und gesehen, wie es in Java implementiert wird. Wir haben auch einige seiner Vor- und Nachteile besprochen.

Wie üblich ist der Quellcode für diesen Artikel auf Github verfügbar.