Entwurfsmuster (Design Patterns): Verhaltensmuster in Java
Thema:
Autor:
E-Mail:
Web:
Thema dieses Blog-Artikels sind die Verhaltensmuster (Behavioral Patterns), die als einer von drei Grundtypen innerhalb der Entwurfsmuster (Design Patterns) angesiedelt sind.
Entwurfsmuster (Design Patterns) sind in der Softwarearchitektur und -entwicklung allgemeine, wiederverwendbare und bewährte Lösungsmuster für wiederkehrende Entwurfsprobleme. Sie bilden somit eine wiederverwendbare Vorlage zur Problemlösung, welche in einem bestimmten Kontext eingesetzt werden kann. Es existieren drei unterschiedliche Grundtypen von Entwurfsmustern (Design Patterns): Erzeugungsmuster (Creational Patterns), Strukturmuster (Structural Patterns) und Verhaltensmuster (Behavioral Patterns).
Verhaltensmuster modellieren komplexes Verhalten der Software und erhöhen dadurch ihre Flexibilität hinsichtlich ihres Verhaltens.
Beispiele für Verhaltensmuster sind: Beobachter, Besucher, Iterator, Schablonenmethode und Strategie.
Nachfolgend wird für zwei Verhaltensmuster (Besucher und Strategie) jeweils ein entsprechendes UML Diagramm dargestellt und erläutert sowie eine beispielhafte Implementierung aufgezeigt. Die Implementierung erfolgt in der objektorientierten Programmiersprache Java.
Besucher:
Das Entwurfsmuster „Besucher“ (visitor pattern) kapselt eine auf den Elementen einer Objektstruktur auszuführende Operation als ein Objekt. Es ermöglicht eine neue Operation zu definieren, ohne die Klassen der von ihr bearbeiteten Elemente zu verändern.
Es wird eingesetzt, wenn
- viele unterschiedliche, nicht verwandte Operationen auf einer Objektstruktur realisiert werden sollen,
- sich die Klassen der Objektstruktur nicht verändern,
- häufig neue Operationen auf der Objektstruktur integriert werden müssen oder
- ein Algorithmus über die Klassen einer Objektstruktur verteilt arbeitet, aber zentral verwaltet werden soll.
Das UML Diagramm für das Entwurfsmuster „Besucher“ stellt sich wie folgt dar:
UML Diagramm für das Entwurfsmuster "Besucher"
Die Akteure des Entwurfsmusters „Besucher“ sind wie folgt:
Visitor (Besucher):
Deklariert für jede Klasse konkreter Elemente eine Besuchsfunktion.
ConcreteVisitor (KonkreterBesucher):
Implementiert Besuchsfunktionen; jede Besuchsfunktion ist ein Teil des Algorithmus, der auf die gesamte Objektstruktur angewendet wird.
Element (Element):
Deklariert eine Schnittstelle für den Empfang eines Besuchers
ConcreteElement (KonkretesElement):
Implementiert den Empfang eines Besuchers
ObjectStructure (Objektstruktur):
Kollektion oder zusammengesetzte Objektstruktur.
Client (Klient):
Referenziert auf die Schnittstelle des Besuchers und durchläuft die Elemente der Objektstruktur.
Eine beispielhafte Java-Implementierung für das Entwurfsmuster „Besucher“ sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
/* * Design Pattern: Visitor Pattern (Besucher) * Modified code example based on the english wikipedia article: Visitor pattern * URL: https://en.wikipedia.org/wiki/Visitor_pattern * * Company: HyperCube IT Solutions * Author: Christian Paulus * Email: c.paulus@hypercube.biz * Web: www.hypercube.biz */ package design_patterns.behavioral_patterns; /* Client (Klient) */ class VisitorPatternClient { public static void main(String args[]) { System.out.println("== Visitor Pattern Application =="); System.out.println(); /* Visitor */ CarElectronicElementVisitor carElectronicElementVisitor = null; /* ConcreteVisitor1 */ carElectronicElementVisitor = new CarElectronicElementPrintDataVisitor(); /* ObjectStructure */ CarElectronic carElectronic = new CarElectronic(); // get 'Elements' to visit from 'ObjectStructure' CarElectronicElement[] carElectronicElements = carElectronic.getCarElectronicElements(); // visit 'Elements' with 'ConcreteVisitor1' System.out.println("ConcreteVisitor1 (CarElectronicElementPrintDataVisitor):"); for (CarElectronicElement carElectronicElement : carElectronicElements) { carElectronicElement.accept(carElectronicElementVisitor); } System.out.println(); /* ConcreteVisitor2 */ carElectronicElementVisitor = new CarElectronicElementSaveDataVisitor(); // visit 'Elements' with 'ConcreteVisitor2' System.out.println("ConcreteVisitor2 (CarElectronicElementSaveDataVisitor):"); for (CarElectronicElement carElectronicElement : carElectronicElements) { carElectronicElement.accept(carElectronicElementVisitor); } } } /* Visitor (Besucher) */ interface CarElectronicElementVisitor { public void visit(BoardComputer boardComputer); // visitConcreteElementA( in a: ConcreteElementA) public void visit(EngineControlUnit engineControlUnit); // visitConcreteElementB( in b: ConcreteElementB) public void visit(WheelSensor wheelSensor); // visitConcreteElementC( in c: ConcreteElementC) } /* ConcreteVisitor1 (KonkreterBesucher1) */ class CarElectronicElementPrintDataVisitor implements CarElectronicElementVisitor { public void visit(final BoardComputer boardComputer) { // visitConcreteElementA( in a: ConcreteElementA) System.out.println("Visiting 'board computer': Print data..."); } public void visit(final EngineControlUnit engineControlUnit) { // visitConcreteElementB( in b: ConcreteElementB) System.out.println("Visiting 'engine control unit': Print data..."); } public void visit(final WheelSensor wheelSensor) { // visitConcreteElementC( in c: ConcreteElementC) System.out.println("Visiting '" + wheelSensor.getName() + " wheel sensor': Print data..."); } } /* ConcreteVisitor2 (KonkreterBesucher2) */ class CarElectronicElementSaveDataVisitor implements CarElectronicElementVisitor { public void visit(final BoardComputer boardComputer) { // visitConcreteElementA( in a: ConcreteElementA) System.out.println("Visiting 'board computer': Save data..."); } public void visit(final EngineControlUnit engineControlUnit) { // visitConcreteElementB( in b: ConcreteElementB) System.out.println("Visiting 'engine control unit': Save data..."); } public void visit(final WheelSensor wheelSensor) { // visitConcreteElementC( in c: ConcreteElementC) System.out.println("Visiting '" + wheelSensor.getName() + " wheel sensor': Save data..."); } } /* ObjectStructure (Objektstruktur) */ class CarElectronic { private CarElectronicElement[] carElectronicElements; public CarElectronic() { this.carElectronicElements = new CarElectronicElement[] { new BoardComputer(), new EngineControlUnit(), new WheelSensor("front left"), new WheelSensor("front right"), new WheelSensor("back left"), new WheelSensor("back right"), }; } public CarElectronicElement[] getCarElectronicElements() { return this.carElectronicElements; } } /* Element (Element) */ interface CarElectronicElement { public void accept(CarElectronicElementVisitor visitor); // accept( in visitor: Visitor) } /* ConcreteElementA (KonkretesElementA) */ class BoardComputer implements CarElectronicElement { public void accept(final CarElectronicElementVisitor visitor) { // accept( in visitor: Visitor) visitor.visit(this); } } /* ConcreteElementB (KonkretesElementB) */ class EngineControlUnit implements CarElectronicElement { public void accept(final CarElectronicElementVisitor visitor) { // accept( in visitor: Visitor) visitor.visit(this); } } /* ConcreteElementC (KonkretesElementC) */ class WheelSensor implements CarElectronicElement { private String name; public WheelSensor(final String name) { this.name = name; } public void accept(final CarElectronicElementVisitor visitor) { // accept( in visitor: Visitor) visitor.visit(this); } public String getName() { // operationC() return name; } } |
Für die Übersichtlichkeit dieses Beispiels wurden alle Java-Klassen in einer Java-Datei aufgeführt, anstatt wie üblich jede Java-Klasse in einer eigenen Java-Datei aufzuführen.
Die Ausführung der Java-Datei VisitorPattern.java in der Eclipse IDE liefert das folgende Ergebnis:
Ausführung von "VisitorPattern.java" in Eclipse
Strategie
Das Entwurfsmuster „Strategie“ (strategy pattern) definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Es ermöglicht den Algorithmus unabhängig von ihn nutzenden Klienten zu variieren.
Es wird eingesetzt, wenn
- viele verwandte Klassen sich nur in ihrem Verhalten unterscheiden,
- unterschiedliche (austauschbare) Varianten eines Algorithmus benötigt werden,
- Daten innerhalb eines Algorithmus vor Klienten verborgen werden sollen oder
- verschiedene Verhaltensweisen innerhalb einer Klasse fest integriert sind, aber die verwendeten Algorithmen wiederverwendet werden sollen bzw. die Klasse flexibler gestaltet werden soll.
Das UML Diagramm für das Entwurfsmuster „Strategie“ stellt sich wie folgt dar:
UML Diagramm für das Entwurfsmuster "Strategie"
Die Akteure des Entwurfsmusters „Strategie“ sind wie folgt:
Strategy (Strategie):
Definiert eine gemeinsame Schnittstelle für alle unterstützten Algorithmen. Der „Kontext“ (Context) verwendet diese Schnittstelle um den Algorithmus aufzurufen, welcher von einer „konkreten Strategie“ (ConcreteStrategy) definiert wurde.
ConcreteStrategy (KonkreteStrategie):
Implementiert den Algorithmus unter Verwendung der Strategie-Schnittstelle.
Context (Kontext):
Hält eine Variable der Schnittstelle Strategie, die mit einer Referenz auf das gewünschte Objekt der „konkreten Strategie“ (ConcreteStrategy) belegt ist. Auf diese Weise wird der konkrete Algorithmus über die Schnittstelle eingebunden und kann bei Bedarf selbst zur Laufzeit noch dynamisch gegen eine andere Implementierung ausgetauscht werden.
Client (Klient):
Referenziert auf die Schnittstelle der Strategie und verwendet den Kontext.
Eine beispielhafte Java-Implementierung für das Entwurfsmuster „Strategie“ sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
/* * Design Pattern: Strategy Pattern (Strategie) * Modified code example based on the english wikipedia article: Strategy pattern * URL: https://en.wikipedia.org/wiki/Strategy_pattern * * Company: HyperCube IT Solutions * Author: Christian Paulus * Email: c.paulus@hypercube.biz * Web: www.hypercube.biz */ package design_patterns.behavioral_patterns; import java.util.List; import java.util.ArrayList; /* Client (Klient) */ class StrategyPatternClient { public static void main(String args[]) { System.out.println("== Strategy Pattern Application =="); System.out.println(); /* Context with ConcreteStrategyA */ Customer firstCustomer = new Customer(new RegularStrategy()); firstCustomer.addOrder(4.0, 1); /* ConcreteStrategyB */ firstCustomer.setStrategy(new SaturdayAfternoonStrategy()); firstCustomer.addOrder(4.0, 1); firstCustomer.addOrder(4.0, 1); /* ConcreteStrategyC */ firstCustomer.setStrategy(new HappyHourStrategy()); firstCustomer.addOrder(4.0, 1); firstCustomer.addOrder(4.0, 1); firstCustomer.addOrder(4.0, 1); /* ConcreteStrategyA */ firstCustomer.setStrategy(new RegularStrategy()); firstCustomer.addOrder(4.0, 1); // First customer pays bill System.out.println("Print bill for 'first customer':"); firstCustomer.printBill(); // ------------------------------------------------------------- System.out.println(); /* (New) Context with ConcreteStrategyB */ Customer secondCustomer = new Customer(new SaturdayAfternoonStrategy()); secondCustomer.addOrder(4.0, 3); secondCustomer.addOrder(3.8, 1); secondCustomer.addOrder(3.4, 1); secondCustomer.addOrder(4.2, 1); /* ConcreteStrategyC */ secondCustomer.setStrategy(new HappyHourStrategy()); secondCustomer.addOrder(5.0, 3); secondCustomer.addOrder(5.0, 3); secondCustomer.addOrder(3.5, 2); secondCustomer.addOrder(4.0, 1); /* ConcreteStrategyA */ secondCustomer.setStrategy(new RegularStrategy()); secondCustomer.addOrder(3.2, 2); secondCustomer.addOrder(3.5, 1); /* ConcreteStrategyC */ secondCustomer.setStrategy(new HappyHourStrategy()); secondCustomer.addOrder(7.0, 3); // Second customer pays bill System.out.println("Print bill for 'second customer':"); secondCustomer.printBill(); } } /* Context (Kontext) */ class Customer { private List<Double> subtotalsOfOrders; private BillingStrategy strategy; // strategy: Strategy public Customer(BillingStrategy strategy) { this.subtotalsOfOrders = new ArrayList<Double>(); this.strategy = strategy; } public void setStrategy(BillingStrategy strategy) { this.strategy = strategy; } public void addOrder(double price, int quantity) { // contextInterface() double regularPrice = price * quantity; subtotalsOfOrders.add( Math.round(strategy.getActualPrice(regularPrice) * 100.0) / 100.0 ); } public void printBill() { double totalPrice = 0; for (Double subtotalOfOrder : subtotalsOfOrders) { System.out.println("Subtotal: " + subtotalOfOrder); totalPrice += subtotalOfOrder; } totalPrice = Math.round(totalPrice * 100.0) / 100.0; System.out.println("Total price: " + totalPrice); subtotalsOfOrders.clear(); } } /* Strategy (Strategie) */ interface BillingStrategy { public double getActualPrice(double regularPrice); // algorithmInterface() } /* ConcreteStrategyA (KonkreteStrategieA) */ class RegularStrategy implements BillingStrategy { public double getActualPrice(double regularPrice) { // algorithmInterface() return regularPrice; } } /* ConcreteStrategyB (KonkreteStrategieB) */ class SaturdayAfternoonStrategy implements BillingStrategy { public double getActualPrice(double regularPrice) { // algorithmInterface() return regularPrice * 0.75; // 25% discount } } /* ConcreteStrategyC (KonkreteStrategieC) */ class HappyHourStrategy implements BillingStrategy { public double getActualPrice(double regularPrice) { // algorithmInterface() return regularPrice * 0.5; // 50% discount } } |
Für die Übersichtlichkeit dieses Beispiels wurden alle Java-Klassen in einer Java-Datei aufgeführt, anstatt wie üblich jede Java-Klasse in einer eigenen Java-Datei aufzuführen.
Die Ausführung der Java-Datei StrategyPattern.java in der Eclipse IDE liefert das folgende Ergebnis:
Ausführung von "StrategyPattern.java" in Eclipse
Quellenangaben:
- Gamma, Erich et al. – Design Patterns: Elements of Reusable Object-Oriented Software; Addison-Wesley, 2009
- Gamma, Erich et al. – Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software; Addison-Wesley, 2011
- Gamma, Erich et al. – Design Patterns: Entwurfsmuster als Elemente wiederverwendbarer objektorientierter Software; mitp, 2015
- Wikipedia – Visitor pattern: https://en.wikipedia.org/wiki/Visitor_pattern
- Wikipedia – Besucher (Entwurfsmuster): https://de.wikipedia.org/wiki/Besucher_(Entwurfsmuster)
- Wikipedia – Strategy pattern: https://en.wikipedia.org/wiki/Strategy_pattern
- Wikipedia – Strategie (Entwurfsmuster): https://de.wikipedia.org/wiki/Strategie_(Entwurfsmuster)