Geek-Zeit - die Schönheit von Entwurfsmustern Builder-Modus: Detaillierte Erläuterung der drei Methoden zur Objekterstellung: Konstruktor, Set-Methode und Builder-Modus

Builder- Modus, Chinesisch übersetzt als Builder-Modus oder Builder-Modus, manche Leute nennen es Builder-Modus.

Tatsächlich sind das Prinzip und die Code-Implementierung des Builder-Musters sehr einfach und nicht schwer zu beherrschen. Die Schwierigkeit liegt in den Anwendungsszenarien. Haben Sie beispielsweise die folgenden Fragen berücksichtigt: Sie können Objekte direkt mit dem Konstruktor oder mit der set-Methode erstellen. Warum benötigen Sie den Builder-Modus, um sie zu erstellen? Sowohl im Builder-Modus als auch im Factory-Modus können Objekte erstellt werden. Was ist also der Unterschied zwischen beiden?

Warum wird der Builder-Modus benötigt?

In der normalen Entwicklung besteht die häufigste Methode zum Erstellen eines Objekts darin, den Konstruktor der Klasse mit dem neuen Schlüsselwort aufzurufen. Meine Frage ist, unter welchen Umständen diese Methode nicht anwendbar ist und das Builder-Muster zum Erstellen von Objekten benötigt wird. Sie können zuerst darüber nachdenken und sich von mir anhand eines Beispiels zeigen lassen.

Angenommen, es gibt eine solche Frage zum Entwurfsinterview: Wir müssen eine Ressourcenpool-Konfigurationsklasse ResourcePoolConfig definieren. Der Ressourcenpool kann hier einfach als Thread-Pool, Verbindungspool, Objektpool usw. verstanden werden. In dieser Konfigurationsklasse für Ressourcenpools gibt es die folgenden Elementvariablen, die konfigurierbare Elemente sind. Schreiben Sie jetzt Code, um diese ResourcePoolConfig-Klasse zu implementieren.

Fügen Sie hier eine Bildbeschreibung ein
Solange Sie ein wenig Entwicklungserfahrung haben, ist es für Sie nicht schwierig, eine solche Klasse zu implementieren. Die gängigsten und am einfachsten zu denkenden Implementierungsideen sind im folgenden Code aufgeführt. Da maxTotal, maxIdle und minIdle keine erforderlichen Variablen sind, übergeben wir beim Erstellen des ResourcePoolConfig-Objekts Nullwerte an diese Parameter im Konstruktor, um die Verwendung von Standardwerten anzugeben.


public class ResourcePoolConfig {
    
    
  private static final int DEFAULT_MAX_TOTAL = 8;
  private static final int DEFAULT_MAX_IDLE = 8;
  private static final int DEFAULT_MIN_IDLE = 0;

  private String name;
  private int maxTotal = DEFAULT_MAX_TOTAL;
  private int maxIdle = DEFAULT_MAX_IDLE;
  private int minIdle = DEFAULT_MIN_IDLE;

  public ResourcePoolConfig(String name, Integer maxTotal, Integer maxIdle, Integer minIdle) {
    
    
    if (StringUtils.isBlank(name)) {
    
    
      throw new IllegalArgumentException("name should not be empty.");
    }
    this.name = name;

    if (maxTotal != null) {
    
    
      if (maxTotal <= 0) {
    
    
        throw new IllegalArgumentException("maxTotal should be positive.");
      }
      this.maxTotal = maxTotal;
    }

    if (maxIdle != null) {
    
    
      if (maxIdle < 0) {
    
    
        throw new IllegalArgumentException("maxIdle should not be negative.");
      }
      this.maxIdle = maxIdle;
    }

    if (minIdle != null) {
    
    
      if (minIdle < 0) {
    
    
        throw new IllegalArgumentException("minIdle should not be negative.");
      }
      this.minIdle = minIdle;
    }
  }
  //...省略getter方法...
}

Jetzt hat ResourcePoolConfig nur 4 konfigurierbare Elemente, die dem Konstruktor entsprechen, es gibt nur 4 Parameter und die Anzahl der Parameter ist gering. Wenn die konfigurierbaren Elemente jedoch allmählich zunehmen und 8, 10 oder sogar mehr werden, folgen Sie weiterhin den aktuellen Entwurfsideen, die Parameterliste des Konstruktors wird sehr lang und der Code ist lesbarer und benutzerfreundlicher. Es wird schlimmer werden. Bei Verwendung des Konstruktors kann es leicht passieren, dass wir die Reihenfolge der Parameter falsch verstehen und die falschen Parameterwerte übergeben, was zu sehr versteckten Fehlern führt.


// 参数太多,导致可读性差、参数可能传递错误
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20falsetrue);

Sie sollten bereits über die Lösung dieses Problems nachgedacht haben, und zwar mithilfe der Funktion set (), um Mitgliedsvariablen zuzuweisen, die den langwierigen Konstruktor ersetzen sollen. Schauen wir uns den Code direkt an, wie unten gezeigt. Unter diesen ist der Name des Konfigurationselements erforderlich, daher setzen wir ihn in den Konstruktor ein, um ihn festzulegen, und er muss beim Erstellen eines Klassenobjekts ausgefüllt werden. Andere Konfigurationselemente maxTotal, maxIdle, minIdle sind nicht erforderlich. Daher legen wir sie über die Funktion set () fest, sodass Benutzer wählen können, ob sie ausfüllen oder nicht ausfüllen möchten.


public class ResourcePoolConfig {
    
    
  private static final int DEFAULT_MAX_TOTAL = 8;
  private static final int DEFAULT_MAX_IDLE = 8;
  private static final int DEFAULT_MIN_IDLE = 0;

  private String name;
  private int maxTotal = DEFAULT_MAX_TOTAL;
  private int maxIdle = DEFAULT_MAX_IDLE;
  private int minIdle = DEFAULT_MIN_IDLE;
  
  public ResourcePoolConfig(String name) {
    
    
    if (StringUtils.isBlank(name)) {
    
    
      throw new IllegalArgumentException("name should not be empty.");
    }
    this.name = name;
  }

  public void setMaxTotal(int maxTotal) {
    
    
    if (maxTotal <= 0) {
    
    
      throw new IllegalArgumentException("maxTotal should be positive.");
    }
    this.maxTotal = maxTotal;
  }

  public void setMaxIdle(int maxIdle) {
    
    
    if (maxIdle < 0) {
    
    
      throw new IllegalArgumentException("maxIdle should not be negative.");
    }
    this.maxIdle = maxIdle;
  }

  public void setMinIdle(int minIdle) {
    
    
    if (minIdle < 0) {
    
    
      throw new IllegalArgumentException("minIdle should not be negative.");
    }
    this.minIdle = minIdle;
  }
  //...省略getter方法...
}

Schauen wir uns als Nächstes an, wie die neue ResourcePoolConfig-Klasse verwendet wird. Ich habe einen Beispielcode wie unten gezeigt geschrieben. Ohne langwierige Funktionsaufrufe und Parameterlisten ist der Code viel lesbarer und benutzerfreundlicher.


// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);

Bisher haben wir den Builder-Modus noch nicht verwendet. Durch Festlegen der erforderlichen Elemente über den Konstruktor und optionaler Konfigurationselemente über die set () -Methode können wir unsere Entwurfsanforderungen erfüllen. Wenn wir beispielsweise das Problem erschweren, müssen wir die folgenden drei Probleme noch lösen, dann können die aktuellen Entwurfsideen nicht erfüllt werden.

● Wie bereits erwähnt, ist ein Name erforderlich, daher fügen wir ihn in den Konstruktor ein und legen ihn fest, wenn ein Objekt zwangsweise erstellt wird. Wenn viele erforderliche Konfigurationselemente vorhanden sind, fügen Sie diese erforderlichen Konfigurationselemente in den Konstruktor ein. Der Konstruktor verfügt dann über eine lange Parameterliste. Wenn wir die erforderlichen Elemente mit der set () -Methode festlegen, kann die Logik zum Überprüfen, ob diese erforderlichen Elemente ausgefüllt wurden, nirgends platziert werden.

● Außerdem wird davon ausgegangen, dass zwischen Konfigurationselementen bestimmte Abhängigkeiten bestehen. Wenn der Benutzer beispielsweise maxTotal, maxIdle und minIdle festlegt, müssen die beiden anderen explizit festgelegt werden, oder es bestehen bestimmte Einschränkungen zwischen Konfigurationselementen Die Bedingungen, z. B. maxIdle und minIdle, müssen kleiner oder gleich maxTotal sein. Wenn wir weiterhin die aktuellen Entwurfsideen verwenden, wird die Überprüfungslogik der Abhängigkeiten oder Einschränkungen zwischen diesen Konfigurationselementen nicht mehr platziert.

● Wenn das ResourcePoolConfig-Klassenobjekt ein unveränderliches Objekt sein soll, dh nachdem das Objekt erstellt wurde, können die internen Eigenschaftswerte nicht geändert werden. Um diese Funktionalität zu erreichen, können wir die set () -Methode in der ResourcePoolConfig-Klasse nicht verfügbar machen.

Um diese Probleme zu lösen, ist der Builder-Modus praktisch.


public class ResourcePoolConfig {
    
    
  private String name;
  private int maxTotal;
  private int maxIdle;
  private int minIdle;

  private ResourcePoolConfig(Builder builder) {
    
    
    this.name = builder.name;
    this.maxTotal = builder.maxTotal;
    this.maxIdle = builder.maxIdle;
    this.minIdle = builder.minIdle;
  }
  //...省略getter方法...

  //我们将Builder类设计成了ResourcePoolConfig的内部类。
  //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
  public static class Builder {
    
    
    private static final int DEFAULT_MAX_TOTAL = 8;
    private static final int DEFAULT_MAX_IDLE = 8;
    private static final int DEFAULT_MIN_IDLE = 0;

    private String name;
    private int maxTotal = DEFAULT_MAX_TOTAL;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;

    public ResourcePoolConfig build() {
    
    
      // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
      if (StringUtils.isBlank(name)) {
    
    
        throw new IllegalArgumentException("...");
      }
      if (maxIdle > maxTotal) {
    
    
        throw new IllegalArgumentException("...");
      }
      if (minIdle > maxTotal || minIdle > maxIdle) {
    
    
        throw new IllegalArgumentException("...");
      }

      return new ResourcePoolConfig(this);
    }

    public Builder setName(String name) {
    
    
      if (StringUtils.isBlank(name)) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.name = name;
      return this;
    }

    public Builder setMaxTotal(int maxTotal) {
    
    
      if (maxTotal <= 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.maxTotal = maxTotal;
      return this;
    }

    public Builder setMaxIdle(int maxIdle) {
    
    
      if (maxIdle < 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.maxIdle = maxIdle;
      return this;
    }

    public Builder setMinIdle(int minIdle) {
    
    
      if (minIdle < 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.minIdle = minIdle;
      return this;
    }
  }
}

// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .setMaxIdle(10)
        .setMinIdle(12)
        .build();

Durch die Verwendung des Builder-Modus zum Erstellen eines Objekts kann auch der ungültige Status des Objekts vermieden werden. Lassen Sie mich mit einem anderen Beispiel erklären. Wenn wir beispielsweise eine rechteckige Klasse definieren, den Builder-Modus nicht verwenden und die Methode zum Erstellen und anschließenden Setzen verwenden, befindet sich das Objekt nach dem ersten Satz in einem ungültigen Zustand. Der spezifische Code lautet wie folgt:


Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid

Um das Vorhandensein dieses ungültigen Zustands zu vermeiden, müssen wir den Konstruktor verwenden, um alle Mitgliedsvariablen gleichzeitig zu initialisieren. Wenn der Konstruktor zu viele Parameter enthält, müssen Sie die Verwendung des Builder-Modus in Betracht ziehen, zuerst die Variablen des Builders festlegen und dann das Objekt einmal erstellen, damit sich das Objekt immer in einem gültigen Zustand befindet.

Wenn es uns nicht wichtig ist, ob das Objekt einen vorübergehend ungültigen Zustand hat, ist es uns auch egal, ob das Objekt veränderlich ist. Wenn das Objekt beispielsweise nur zum Zuordnen der aus der Datenbank ausgelesenen Daten verwendet wird, machen wir die set () -Methode direkt verfügbar, um den Wert der Mitgliedsvariablen der Klasse festzulegen. Wenn Sie den Builder-Modus zum Erstellen von Objekten verwenden, wiederholt sich der Code tatsächlich ein wenig. Die Elementvariablen in der ResourcePoolConfig-Klasse müssen in der Builder-Klasse neu definiert werden. Was ist der Unterschied zum Werksmodell?

Was ist der Unterschied zum Werksmodell?

Aus der obigen Erklärung können wir ersehen, dass der Builder-Modus darin besteht, dass die Builder-Klasse für die Erstellung des Objekts verantwortlich ist. In dem in der vorherigen Lektion erwähnten Factory-Muster ist die Factory-Klasse für die Erstellung von Objekten verantwortlich. Was ist der Unterschied zwischen ihnen?

Tatsächlich wird das Factory-Muster verwendet, um verschiedene, aber verwandte Objekttypen zu erstellen (eine Gruppe von Unterklassen, die dieselbe übergeordnete Klasse oder Schnittstelle erben), und die angegebenen Parameter bestimmen, welcher Objekttyp erstellt werden soll. Der Builder-Modus wird verwendet, um einen Typ eines komplexen Objekts zu erstellen, indem verschiedene optionale Parameter "angepasst" werden, um verschiedene Objekte zu erstellen.

Es gibt ein klassisches Beispiel im Internet, das den Unterschied zwischen beiden erklärt.

Kunden gehen in ein Restaurant, um zu bestellen. Wir verwenden das Fabrikmodell, um verschiedene Lebensmittel wie Pizza, Burger und Salate zuzubereiten, je nach Auswahl der Benutzer. Für Pizza müssen Benutzer verschiedene Zutaten anpassen, z. B. Käse, Tomaten und Käse. Wir verwenden den Builder-Modus, um Pizza gemäß den verschiedenen vom Benutzer ausgewählten Zutaten zuzubereiten.

Tatsächlich sollten wir nicht zu akademisch sein. Wir müssen das Fabrikmodell und das Baumodell so klar unterscheiden. Wir müssen wissen, warum jedes Modell so konstruiert ist und welche Probleme es lösen kann. Nur wenn wir diese wichtigsten Dinge verstehen, können wir sie flexibel anwenden, ohne sie zu kopieren, und wir können sogar verschiedene Modelle mischen, um neue Modelle zur Lösung von Problemen in bestimmten Szenarien zu erstellen.

Der Builder löst hauptsächlich die Probleme übermäßiger Parameter, Parameterprüfung und Unveränderlichkeit von Steuerobjekten nach der Erstellung

Ich denke du magst

Origin blog.csdn.net/zhujiangtaotaise/article/details/110443556
Empfohlen
Rangfolge