Wir haben die Verwendung eines Singleton-Objekts bereits besprochen, aber Sie wissen vielleicht noch nicht, dass es sich bei dieser Strategie um ein Entwurfsmuster handelt, und zwar um eines der am häufigsten verwendeten.

Tatsächlich gibt es viele dieser Muster und sie können nach ihrem spezifischen Zweck klassifiziert werden.

Musterklassifizierung

Muster-Art Anwendung
Kreativ Ein Typ, der das Problem der Objekterstellung löst
Strukturell Muster, die es uns ermöglichen, eine korrekte und erweiterbare Klassenhierarchie in unserer Architektur aufzubauen
Verhalten Diese Mustergruppe ermöglicht eine sichere und bequeme Interaktion zwischen Objekten in einem Programm.

Typischerweise wird ein Muster durch das Problem charakterisiert, das es löst. Werfen wir einen Blick auf einige Muster, die uns bei der Arbeit mit Java am häufigsten begegnen:

Muster Zweck
Singleton Wir kennen dieses Muster bereits – wir verwenden es, um eine Klasse zu erstellen und darauf zuzugreifen, die nicht mehr als eine Instanz haben kann.
Iterator Auch diesen kennen wir. Wir wissen, dass wir mit diesem Muster ein Sammlungsobjekt durchlaufen können, ohne seine interne Darstellung preiszugeben. Es wird mit Sammlungen verwendet.
Adapter Dieses Muster verbindet inkompatible Objekte, sodass sie zusammenarbeiten können. Ich denke, der Name des Adaptermusters hilft Ihnen, sich genau vorzustellen, was es bewirkt. Hier ist ein einfaches Beispiel aus dem wirklichen Leben: ein USB-Adapter für eine Steckdose.
Vorlagenmethode

Ein Verhaltensprogrammiermuster, das das Integrationsproblem löst und es Ihnen ermöglicht, Algorithmusschritte zu ändern, ohne die Struktur eines Algorithmus zu ändern.

Stellen Sie sich vor, wir hätten einen Automontagealgorithmus in Form einer Folge von Montageschritten:

Fahrgestell -> Karosserie -> Motor -> Kabineninnenraum

Wenn wir einen verstärkten Rahmen, einen stärkeren Motor oder einen Innenraum mit zusätzlicher Beleuchtung einbauen, müssen wir den Algorithmus nicht ändern und der abstrakte Ablauf bleibt derselbe.

Dekorateur Dieses Muster erstellt Wrapper für Objekte, um ihnen nützliche Funktionen zu verleihen. Wir werden es im Rahmen dieses Artikels betrachten.

In Java.io implementieren die folgenden Klassen Muster:

Muster Wo es in java.io verwendet wird
Adapter
Vorlagenmethode
Dekorateur

Dekorationsmuster

Stellen wir uns vor, wir beschreiben ein Modell für ein Wohndesign.

Generell sieht die Vorgehensweise so aus:

Zunächst haben wir die Wahl zwischen mehreren Haustypen. Die Mindestkonfiguration ist eine Etage mit Dach. Dann verwenden wir alle möglichen Dekorateure, um zusätzliche Parameter zu ändern, was sich natürlich auf den Preis des Hauses auswirkt.

Wir erstellen eine abstrakte House-Klasse:

public abstract class House {
	String info;

	public String getInfo() {
    	return info;
	}

	public abstract int getPrice();
}

Hier haben wir 2 Methoden:

  • getInfo() gibt Informationen über den Namen und die Ausstattung unseres Hauses zurück;
  • getPrice() gibt den Preis der aktuellen Hauskonfiguration zurück.

Wir haben auch Standard-Hausausführungen – Ziegel und Holz:

public class BrickHouse extends House {

	public BrickHouse() {
    	info = "Brick House";
	}

	@Override
	public int getPrice() {
    	return 20_000;
	}
}

public class WoodenHouse extends House {

	public WoodenHouse() {
    	info = "Wooden House";
	}

	@Override
	public int getPrice() {
    	return 25_000;
	}
}

Beide Klassen erben die Klasse House und überschreiben deren Preismethode, indem sie einen benutzerdefinierten Preis für ein Standardhaus festlegen. Den Namen legen wir im Konstruktor fest.

Als nächstes müssen wir Dekorateurklassen schreiben. Diese Klassen erben auch die House- Klasse. Dazu erstellen wir eine abstrakte Dekoratorklasse.

Hier werden wir zusätzliche Logik zum Ändern eines Objekts einfügen. Zunächst gibt es keine zusätzliche Logik und die abstrakte Klasse ist leer.

abstract class HouseDecorator extends House {
}

Als nächstes erstellen wir Dekorator-Implementierungen. Wir werden mehrere Klassen erstellen, mit denen wir dem Haus zusätzliche Funktionen hinzufügen können:

public class SecondFloor extends HouseDecorator {
	House house;

	public SecondFloor(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 20_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + second floor";
	}
}
Ein Dekorateur, der unserem Haus eine zweite Etage hinzufügt

Der Dekorateurkonstrukteur akzeptiert ein Haus, das wir „dekorieren“, also umbauen. Und wir überschreiben die Methoden getPrice() und getInfo() und geben Informationen über das neue aktualisierte Haus basierend auf dem alten zurück.

public class Garage extends HouseDecorator {

	House house;
	public Garage(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 5_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + garage";
	}
}
Ein Dekorateur, der unserem Haus eine Garage hinzufügt

Jetzt können wir unser Haus mit Dekorateuren modernisieren. Dazu müssen wir ein Haus erstellen:

House brickHouse = new BrickHouse();

Als nächstes stellen wir unsere einHausVariable gleich einem neuen Dekorateur, der in unserem Haus vorbeikommt:

brickHouse = new SecondFloor(brickHouse);

UnserHausVariable ist jetzt ein Haus mit einem zweiten Stock.

Schauen wir uns Anwendungsfälle an, an denen Dekorateure beteiligt sind:

Beispielcode Ausgang
House brickHouse = new BrickHouse();

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Ziegelhaus

20000

House brickHouse = new BrickHouse();

  brickHouse = new SecondFloor(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Backsteinhaus + zweiter Stock

40000

House brickHouse = new BrickHouse();


  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Backsteinhaus + zweiter Stock + Garage

45000

House woodenHouse = new SecondFloor(new Garage(new WoodenHouse()));

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

Holzhaus + Garage + zweiter Stock

50000

House woodenHouse = new WoodenHouse();

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());

Holzhaus

25000

Holzhaus + Garage

30000

Dieses Beispiel veranschaulicht den Vorteil der Aufwertung eines Objekts mit einem Dekorateur. Also haben wir das nicht geändertHolzhausObjekt selbst, sondern erstellte stattdessen ein neues Objekt basierend auf dem alten. Hier sehen wir, dass die Vorteile mit Nachteilen einhergehen: Wir erstellen jedes Mal ein neues Objekt im Speicher, was den Speicherverbrauch erhöht.

Schauen Sie sich dieses UML-Diagramm unseres Programms an:

Ein Dekorateur hat eine supereinfache Implementierung und ändert Objekte dynamisch und wertet sie auf. Dekoratoren können an ihren Konstruktoren erkannt werden, die als Parameter Objekte desselben abstrakten Typs oder derselben Schnittstelle wie die aktuelle Klasse verwenden. In Java wird dieses Muster häufig in I/O-Klassen verwendet.

Wie bereits erwähnt, verfügen beispielsweise alle Unterklassen von java.io.InputStream , OutputStream , Reader und Writer über einen Konstruktor, der Objekte derselben Klassen akzeptiert.