Eines der wichtigsten Prinzipien in der Softwareentwicklung ist das Open-Closed-Design-Prinzip. Dieses Designprinzip betont, dass Klassen für Erweiterungen offen, aber für Änderungen geschlossen sein sollten. Das Designmuster decorator verkörpert das Open-Closed-Design-Prinzip.
Mit dem Decorator-Entwurfsmuster können Sie eine Klasse einfach erweitern, indem Sie ihr ein neues Verhalten geben, ohne ihren vorhandenen Code zu ändern. Das Decorator-Muster tut dies dynamisch zur Laufzeit, indem es die Komposition verwendet. Dieses Entwurfsmuster ist als flexible Alternative zur Verwendung von Vererbung zum Erweitern des Verhaltens bekannt.
Wie funktioniert das Decorator Design Pattern?
Obwohl das Decorator-Muster eine Alternative zu ist Klassenerbe, enthält es einige Aspekte der Vererbung in seinem Design. Ein Schlüsselaspekt des Decorator-Musters besteht darin, dass alle seine Klassen entweder direkt oder indirekt verwandt sind.
Ein typisches Designmuster für Dekorateure hat die folgende Struktur:
Aus dem obigen Klassendiagramm können Sie ersehen, dass das Decorator-Muster vier Hauptklassen hat.
Komponente: Dies ist eine abstrakte Klasse (oder Schnittstelle), die als Obertyp für das Dekorationsmuster dient.
BetonKomponente: Dies sind die Objekte, die Sie zur Laufzeit mit verschiedenen Verhaltensweisen dekorieren können. Sie erben von der Komponentenschnittstelle und implementieren ihre abstrakten Funktionen.
Dekorateur: Diese Klasse ist abstrakt und hat denselben Obertyp wie das Objekt, das sie schmücken wird. Im Klassendiagramm sehen Sie zwei Beziehungen zwischen den Komponenten- und Decorator-Klassen. Die erste Beziehung ist eine der Vererbung; jeder Dekorateur ist ein Komponente. Die zweite Beziehung ist eine der Komposition; jeder Dekorateur hat ein (oder umschließt eine) Komponente.
BetonDekorateur: Dies sind die einzelnen Dekorateure, die einer Komponente ein bestimmtes Verhalten verleihen. Beachten Sie, dass jeder konkrete Dekorator eine Instanzvariable hat, die eine Referenz auf eine Komponente enthält.
Implementieren des Decorator-Entwurfsmusters in Java
Eine beispielhafte Pizzabestellanwendung kann angemessen demonstrieren, wie das Dekorationsmuster verwendet wird, um Anwendungen zu entwickeln. Mit dieser Beispiel-Pizza-Anwendung können Kunden Pizzen mit mehreren Belägen bestellen. Die erste Klasse des Decorator-Musters ist die Pizza-Schnittstelle:
öffentlichSchnittstellePizza{
öffentlichabstrakt Schnur Beschreibung();
öffentlichabstraktdoppeltkosten();
}
Die Pizza-Schnittstelle ist die Komponentenklasse. Sie können daraus also eine oder mehrere konkrete Klassen erstellen. Die Pizzafirma stellt zwei Hauptarten von Pizzen her, basierend auf ihrem Teig. Eine Art Pizza hat Hefeteig:
öffentlichKlasseHefeCrustPizzaimplementiertPizza{
@Überschreiben
öffentlich Schnur Beschreibung(){
zurückkehren"Pizzateig mit Hefe";
}
@Überschreiben
öffentlichdoppeltkosten(){
zurückkehren18.00;
}
}
Die YeastCrustPizza ist die erste konkrete Java-Klasse der Pizza-Schnittstelle. Die andere verfügbare Pizzasorte ist Fladenbrot:
öffentlichKlasseFladenbrotCrustPizzaimplementiertPizza{
@Überschreiben
öffentlich Schnur Beschreibung(){
zurückkehren"Pizzateig aus Fladenbrot";
}
@Überschreiben
öffentlichdoppeltkosten(){
zurückkehren15.00;
}
}
Die FlatbreadCrustPizza-Klasse ist die zweite konkrete Komponente und implementiert wie die YeastCrustPizza-Klasse alle abstrakten Funktionen der Pizza-Schnittstelle.
Die Dekorateure
Die Decorator-Klasse ist immer abstrakt, Sie können also keine neue Instanz direkt daraus erstellen. Aber es ist notwendig, eine Beziehung zwischen den verschiedenen Dekorateuren und den Komponenten herzustellen, die sie dekorieren werden.
öffentlichabstraktKlasseToppingDecoratorimplementiertPizza{
öffentlich Schnur Beschreibung(){
zurückkehren"Unbekannter Belag";
}
}
Die ToppingDecorator-Klasse repräsentiert die Decorator-Klasse in dieser Beispielanwendung. Jetzt kann das Pizzaunternehmen mithilfe der ToppingDecorator-Klasse viele verschiedene Beläge (oder Dekorationen) erstellen. Nehmen wir an, eine Pizza kann drei verschiedene Arten von Belägen haben, nämlich Käse, Peperoni und Pilze.
Käsebelag
öffentlichKlasseKäseerweitertToppingDecorator{
Privatgelände Pizza Pizza;öffentlichKäse(Pizza Pizza){
Das.pizza = Pizza;
}@Überschreiben
öffentlich Schnur Beschreibung(){
zurückkehren pizza.beschreibung() + ", Käsebelag";
}
@Überschreiben
öffentlichdoppeltkosten(){
zurückkehrenPizza.kosten() + 2.50;
}
}
Peperoni-Topping
öffentlichKlassePeperonierweitertToppingDecorator{
Privatgelände Pizza Pizza;öffentlichPeperoni(Pizza Pizza){
Das.pizza = Pizza;
}@Überschreiben
öffentlich Schnur Beschreibung(){
zurückkehren pizza.beschreibung() + ", Peperoni-Topping";
}
@Überschreiben
öffentlichdoppeltkosten(){
zurückkehrenPizza.kosten() + 3.50;
}
}
Pilzbelag
öffentlichKlassePilzerweitertToppingDecorator{
Privatgelände Pizza Pizza;öffentlichPilz(Pizza Pizza){
Das.pizza = Pizza;
}
@Überschreiben
öffentlich Schnur Beschreibung(){
zurückkehren pizza.beschreibung() + ", Pilzbelag";
}
@Überschreiben
öffentlichdoppeltkosten(){
zurückkehrenPizza.kosten() + 4.50;
}
}
Jetzt haben Sie eine einfache Anwendung implementiert, die das Decorator-Entwurfsmuster verwendet. Wenn ein Kunde eine Hefeteigpizza mit Käse und Peperoni bestellt, sieht der Testcode für dieses Szenario wie folgt aus:
öffentlichKlasseHauptsächlich{
öffentlichstatischLeerehauptsächlich(String[] Argumente){
Pizzapizza1 = neu HefeCrustPizza();
pizza1 = neu Peperoni (Pizza1);
pizza1 = neu Käse (Pizza1);
System.out.println (pizza1.description() + " $" + pizza1.Kosten());
}
}
Das Ausführen dieses Codes erzeugt die folgende Ausgabe in der Konsole:
Wie Sie sehen können, gibt die Ausgabe die Art der Pizza zusammen mit ihren Gesamtkosten an. Die Pizza begann als Pizza mit Hefekruste für 18,00 $, aber mit dem Dekorationsmuster konnte die Anwendung der Pizza neue Funktionen und deren angemessene Kosten hinzufügen. So wird der Pizza ein neues Verhalten gegeben, ohne den bestehenden Code (die Pizza mit Hefekruste) zu ändern.
Mit dem Decorator-Muster können Sie dasselbe Verhalten beliebig oft auf ein Objekt anwenden. Wenn ein Kunde eine Pizza mit allem drauf und etwas zusätzlichem Käse bestellt, können Sie die Hauptklasse mit dem folgenden Code aktualisieren, um dies widerzuspiegeln:
Pizzapizza2 = neu HefeCrustPizza();
pizza2 = neu Peperoni (Pizza2);
pizza2 = neu Käse (Pizza2);
pizza2 = neu Käse (Pizza2);
pizza2 = neu Pilz (Pizza2);System.out.println (pizza2.description() + " $" + pizza2.Kosten());
Die aktualisierte Anwendung erzeugt die folgende Ausgabe in der Konsole:
Die Vorteile der Verwendung des Decorator-Entwurfsmusters
Die zwei Hauptvorteile der Verwendung des Decorator-Entwurfsmusters sind Sicherheit und Flexibilität. Mit dem Decorator-Muster können Sie sichereren Code entwickeln, indem Sie nicht in bereits vorhandenen sicheren Code eingreifen. Stattdessen wird vorhandener Code durch Komposition erweitert. Effektive Verhinderung der Einführung neuer Fehler oder unbeabsichtigter Nebenwirkungen.
Aufgrund der Zusammensetzung hat ein Entwickler auch viel Flexibilität bei der Verwendung des Decorator-Musters. Sie können jederzeit einen neuen Decorator implementieren, um neues Verhalten hinzuzufügen, ohne bestehenden Code zu ändern und die Anwendung zu unterbrechen.