Einführung in die kreativen Entwurfsmuster

Einführung in die Erstellung von Entwurfsmustern

1. Einführung

In der Softwareentwicklung beschreibt ein Entwurfsmuster eine etablierte Lösung für die am häufigsten auftretenden Probleme im Softwareentwurf. Es stellt die Best Practices dar, die über einen langen Zeitraum von erfahrenen Softwareentwicklern durch Ausprobieren entwickelt wurden.

Design Patterns wurden immer beliebter, nachdem das BuchDesign Patterns: Elements of Reusable Object-Oriented Software 1994 von Erich Gamma, John Vlissides, Ralph Johnson und Richard Helm (auch bekannt als Gang of Four oder GoF) veröffentlicht wurde.

In diesem Artikel werden wir uns mit kreativen Designmustern und ihren Typen befassen. Wir werden uns auch einige Codebeispiele ansehen und die Situationen diskutieren, in denen diese Muster zu unserem Design passen.

2. Kreative Designmuster

Creational Design Patterns are concerned with the way in which objects are created. Sie reduzieren Komplexität und Instabilität, indem sie Objekte auf kontrollierte Weise erstellen.

Der Operatornewwird häufig als schädlich angesehen, da er Objekte in der gesamten Anwendung streut. Im Laufe der Zeit kann es schwierig werden, eine Implementierung zu ändern, da die Klassen eng miteinander verbunden sind.

Mit Creational Design Patterns wird dieses Problem behoben, indem der Client vollständig vom eigentlichen Initialisierungsprozess entkoppelt wird.

In diesem Artikel werden vier Arten von kreativen Entwurfsmustern erläutert:

  1. Singleton - Stellt sicher, dass in der gesamten Anwendung höchstens eine Instanz eines Objekts vorhanden ist

  2. Factory-Methode - Erstellt Objekte mehrerer zusammengehöriger Klassen, ohne das genaue zu erstellende Objekt anzugeben

  3. Abstrakte Fabrik - Erstellt Familien verwandter abhängiger Objekte

  4. Builder Konstruiert komplexe Objekte schrittweise

Lassen Sie uns nun jedes dieser Muster im Detail diskutieren.

3. Singleton Design Pattern

Das Singleton-Entwurfsmuster zielt darauf ab, die Initialisierung von Objekten einer bestimmten Klasse umensuring that only one instance of the object exists throughout the Java Virtual Machine. zu überprüfen

Eine Singleton-Klasse stellt außerdem einen eindeutigen globalen Zugriffspunkt für das Objekt bereit, sodass bei jedem nachfolgenden Aufruf des Zugriffspunkts nur dieses bestimmte Objekt zurückgegeben wird.

3.1. Beispiel für ein Singleton-Muster

Obwohl das Singleton-Muster von GoF eingeführt wurde, ist bekannt, dass die ursprüngliche Implementierung in Multithread-Szenarien problematisch ist.

Hier verfolgen wir also einen optimaleren Ansatz, bei dem eine statische innere Klasse verwendet wird:

public class Singleton  {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

Hier haben wir eine innere Klasse vonstaticerstellt, die die Instanz der Klasse vonSingletonenthält. Die Instanz wird nur erstellt, wenn jemand die MethodegetInstance()aufruft und nicht, wenn die äußere Klasse geladen ist.

Dies ist ein weit verbreiteter Ansatz für eine Singleton-Klasse, da sie keine Synchronisation erfordert, threadsicher ist, eine verzögerte Initialisierung erzwingt und vergleichsweise weniger Boilerplate hat.

Beachten Sie außerdem, dass der Konstruktor über den Zugriffsmodifikatorprivateverfügt. This is a requirement for creating a Singleton since a public constructor would mean anyone could access it and start creating new instances.

Denken Sie daran, dass dies nicht die ursprüngliche GoF-Implementierung ist. Für die Originalversion besuchen Sie bittethis linked example article auf Singletons in Java.

3.2. Wann wird das Singleton-Entwurfsmuster verwendet?

  • Für Ressourcen, deren Erstellung teuer ist (z. B. Datenbankverbindungsobjekte)

  • Es wird empfohlen, alle Logger als Singletons zu behalten, um die Leistung zu steigern

  • Klassen, die den Zugriff auf Konfigurationseinstellungen für die Anwendung ermöglichen

  • Klassen, die Ressourcen enthalten, auf die im gemeinsam genutzten Modus zugegriffen wird

4. Factory Method Design Pattern

Das Factory Design Pattern oder Factory Method Design Pattern ist eines der am häufigsten verwendeten Entwurfsmuster in Java.

Laut GoF lässt dieses Muster“defines an interface for creating an object, but let subclasses decide which class to instantiate. Mit der Factory-Methode eine Klasse die Instanziierung auf Unterklassen verschieben. “

Dieses Muster delegiert die Verantwortung für das Initialisieren einer Klasse vom Client an eine bestimmte Factory-Klasse, indem ein Typ eines virtuellen Konstruktors erstellt wird.

Um dies zu erreichen, verlassen wir uns auf eine Fabrik, die uns die Objekte zur Verfügung stellt und die tatsächlichen Implementierungsdetails verbirgt. Der Zugriff auf die erstellten Objekte erfolgt über eine gemeinsame Schnittstelle.

4.1. Beispiel für ein Entwurfsmuster für die Werksmethode

In diesem Beispiel erstellen wir einePolygon-Schnittstelle, die von mehreren konkreten Klassen implementiert wird. EinPolygonFactory wird verwendet, um Objekte aus dieser Familie abzurufen:

image

Erstellen wir zunächst diePolygon-Schnittstelle:

public interface Polygon {
    String getType();
}

Als Nächstes erstellen wir einige Implementierungen wieSquare,Triangle, usw. die diese Schnittstelle implementieren und ein Objekt vom TypPolygonzurückgeben.

Jetzt können wir eine Factory erstellen, die die Anzahl der Seiten als Argument verwendet und die entsprechende Implementierung dieser Schnittstelle zurückgibt:

public class PolygonFactory {
    public Polygon getPolygon(int numberOfSides) {
        if(numberOfSides == 3) {
            return new Triangle();
        }
        if(numberOfSides == 4) {
            return new Square();
        }
        if(numberOfSides == 5) {
            return new Pentagon();
        }
        if(numberOfSides == 7) {
            return new Heptagon();
        }
        else if(numberOfSides == 8) {
            return new Octagon();
        }
        return null;
    }
}

Beachten Sie, wie sich der Client auf diese Factory verlassen kann, um uns ein angemessenesPolygonzu geben, ohne das Objekt direkt initialisieren zu müssen.

4.2. Wann wird das Entwurfsmuster für die Werksmethode verwendet?

  • Wenn die Implementierung einer Schnittstelle oder einer abstrakten Klasse voraussichtlich häufig geändert wird

  • Wenn die aktuelle Implementierung neue Änderungen nicht bequem aufnehmen kann

  • Wenn der Initialisierungsprozess relativ einfach ist und der Konstruktor nur eine Handvoll Parameter benötigt

5. Abstraktes Fabrik-Entwurfsmuster

Im vorherigen Abschnitt haben wir gesehen, wie mit dem Entwurfsmuster der Factory-Methode Objekte erstellt werden können, die sich auf eine einzelne Familie beziehen.

Im Gegensatz dazu wird das abstrakte Factory-Entwurfsmuster zum Erstellen von Familien verwandter oder abhängiger Objekte verwendet. Es wird manchmal auch als Fabrik von Fabriken bezeichnet.

Eine ausführliche Erklärung finden Sie in unserem Tutorial zuAbstract Factory.

6. Builder-Entwurfsmuster

Das Builder-Entwurfsmuster ist ein weiteres Entwurfsmuster, das für die Konstruktion von vergleichsweise komplexen Objekten entwickelt wurde.

Wenn die Komplexität beim Erstellen von Objekten zunimmt, kann das Builder-Muster den Instanziierungsprozess trennen, indem ein anderes Objekt (ein Builder) zum Erstellen des Objekts verwendet wird.

Mit diesem Builder können Sie dann mithilfe eines einfachen schrittweisen Ansatzes viele andere ähnliche Darstellungen erstellen.

6.1. Beispiel für ein Builder-Muster

Das von GoF eingeführte ursprüngliche Builder-Entwurfsmuster konzentriert sich auf die Abstraktion und ist sehr gut im Umgang mit komplexen Objekten, das Design ist jedoch etwas kompliziert.

Joshua Bloch hat in seinem Buch Effective Java eine verbesserte Version des Builder-Musters vorgestellt, die sauber, gut lesbar (dafluent design verwendet wird) und aus Kundensicht einfach zu verwenden ist. In diesem Beispiel wird diese Version erläutert.

Dieses Beispiel hat nur eine Klasse,BankAccount, die einen Builder als innere Klasse vonstaticenthält:

public class BankAccount {

    private String name;
    private String accountNumber;
    private String email;
    private boolean newsletter;

    // constructors/getters

    public static class BankAccountBuilder {
        // builder code
    }
}

Beachten Sie, dass alle Zugriffsmodifikatoren in den Feldern alsprivate deklariert sind, da externe Objekte nicht direkt auf sie zugreifen sollen.

Der Konstruktor ist auchprivate, sodass nur der dieser Klasse zugewiesene Builder darauf zugreifen kann. Alle im Konstruktor festgelegten Eigenschaften werden aus dem Builder-Objekt extrahiert, das wir als Argument angeben.

Wir habenBankAccountBuilder in der inneren Klasse vonstaticdefiniert:

public static class BankAccountBuilder {

    private String name;
    private String accountNumber;
    private String email;
    private boolean newsletter;

    public BankAccountBuilder(String name, String accountNumber) {
        this.name = name;
        this.accountNumber = accountNumber;
    }

    public BankAccountBuilder withEmail(String email) {
        this.email = email;
        return this;
    }

    public BankAccountBuilder wantNewsletter(boolean newsletter) {
        this.newsletter = newsletter;
        return this;
    }

    public BankAccount build() {
        return new BankAccount(this);
    }
}

Beachten Sie, dass wir denselben Satz von Feldern deklariert haben, den die äußere Klasse enthält. Alle Pflichtfelder sind als Argumente für den Konstruktor der inneren Klasse erforderlich, während die verbleibenden optionalen Felder mit den Setter-Methoden angegeben werden können.

Diese Implementierung unterstützt auch den fließenden Entwurfsansatz, indem die Setter-Methoden das Builder-Objekt zurückgeben.

Schließlich ruft die build-Methode den privaten Konstruktor der äußeren Klasse auf und übergibt sich selbst als Argument. Die zurückgegebenenBankAccount werden mit den durchBankAccountBuilder festgelegten Parametern instanziiert.

Sehen wir uns ein kurzes Beispiel für das Builder-Muster in Aktion an:

BankAccount newAccount = new BankAccount
  .BankAccountBuilder("Jon", "22738022275")
  .withEmail("[email protected]")
  .wantNewsletter(true)
  .build();

6.2. Wann wird das Builder-Muster verwendet?

  1. Wenn der Prozess zum Erstellen eines Objekts äußerst komplex ist, mit vielen obligatorischen und optionalen Parametern

  2. Wenn eine Erhöhung der Anzahl der Konstruktorparameter zu einer großen Liste von Konstruktoren führt

  3. Wenn der Client unterschiedliche Darstellungen für das erstellte Objekt erwartet

7. Fazit

In diesem Artikel haben wir uns mit kreativen Entwurfsmustern in Java vertraut gemacht. Wir diskutierten auch ihre vier verschiedenen Typen, d. H. Singleton, Factory-Methode, Abstract Factory und Builder-Muster, ihre Vorteile, Beispiele und wann wir sie verwenden sollten.

Wie immer sind die vollständigen Codefragmenteavailable over on GitHub.