Ich habe das geschaffen, was für mich eine große Verbesserung gegenüber Josh Blochs Builder Pattern darstellt. Ganz zu schweigen davon, dass es "besser" ist, nur dass es in einer ganz bestimmten Situation einige Vorteile bietet - das größte ist, dass es den Bauherrn von seiner zu bauenden Klasse entkoppelt.
Ich habe diese Alternative, die ich als Blind Builder Pattern bezeichne, im Folgenden ausführlich dokumentiert.
Entwurfsmuster: Blind Builder
Als Alternative zu Joshua Blochs Builder-Muster (Punkt 2 in Effective Java, 2. Ausgabe) habe ich das sogenannte "Blind Builder-Muster" erstellt, das viele der Vorteile des Bloch Builder teilt und neben einem einzelnen Charakter wird genauso verwendet. Blinde Bauherren haben den Vorteil von
- den Builder von seiner einschließenden Klasse zu entkoppeln, wodurch eine zirkuläre Abhängigkeit beseitigt wird,
- stark reduziert die Größe des Quellcodes (was nicht mehr ist ) der einschließenden Klasse, und
- Ermöglicht das
ToBeBuilt
Erweitern der Klasse, ohne dass der Builder erweitert werden muss .
In dieser Dokumentation werde ich die Klasse, die erstellt wird, als " ToBeBuilt
" Klasse bezeichnen.
Eine Klasse, die mit einem Bloch Builder implementiert wurde
Ein Bloch Builder ist ein public static class
Bestandteil der Klasse, die er erstellt. Ein Beispiel:
öffentliche Klasse UserConfig {
private final String sName;
private final int iAge;
private final String sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//Transfer
Versuchen {
sName = uc_c.sName;
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// ALLE FELDER HIER GÜLTIG MACHEN
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
öffentliche statische Klasse Cfg {
private String sName;
private int iAge;
private String sFavColor;
public Cfg (String s_name) {
sName = s_name;
}
// selbst zurückkehrende Setter ... START
öffentliches Cfg-Alter (int i_age) {
iAge = i_age;
gib das zurück;
}
public Cfg favoriteColor (String s_color) {
sFavColor = s_color;
gib das zurück;
}
// selbst zurückkehrende Setter ... END
public UserConfig build () {
return (new UserConfig (this));
}
}
//Builder ... END
}
Instanziieren einer Klasse mit einem Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
Dieselbe Klasse, implementiert als Blind Builder
Ein Blind Builder besteht aus drei Teilen, die sich jeweils in einer separaten Quellcodedatei befinden:
- Die
ToBeBuilt
Klasse (in diesem Beispiel: UserConfig
)
- Seine "
Fieldable
" Schnittstelle
- Der Bauarbeiter
1. Die zu bauende Klasse
Die zu erstellende Klasse akzeptiert ihre Fieldable
Schnittstelle als einzigen Konstruktorparameter. Der Konstruktor setzt alle internen Felder daraus und validiert sie . Am wichtigsten ist, dass diese ToBeBuilt
Klasse keine Kenntnisse über ihren Builder hat.
öffentliche Klasse UserConfig {
private final String sName;
private final int iAge;
private final String sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//Transfer
Versuchen {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ALLE FELDER HIER GÜLTIG MACHEN
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Wie von einem Smart - Kommentator bemerkt (die aus unerklärlichen Gründen ihre Antwort gelöscht), wenn die ToBeBuilt
Klasse implementiert auch seinen Fieldable
, sein ein-und-nur - Konstruktor kann sowohl als seinen primären verwendet werden und Copy - Konstruktor (ein Nachteil ist , dass die Felder sind zwar immer validiert, auch Es ist bekannt, dass die Felder im Original ToBeBuilt
gültig sind.
2. Die Fieldable
Schnittstelle " "
Die feldfähige Schnittstelle ist die "Brücke" zwischen der ToBeBuilt
Klasse und ihrem Builder und definiert alle Felder, die zum Erstellen des Objekts erforderlich sind. Diese Schnittstelle wird vom ToBeBuilt
Klassenkonstruktor benötigt und vom Builder implementiert. Da diese Schnittstelle von anderen Klassen als dem Builder implementiert werden kann, kann jede Klasse die ToBeBuilt
Klasse leicht instanziieren , ohne gezwungen zu sein, ihren Builder zu verwenden. Dies erleichtert auch das Erweitern der ToBeBuilt
Klasse, wenn das Erweitern des Builders nicht erwünscht oder erforderlich ist.
Wie in einem der folgenden Abschnitte beschrieben, dokumentiere ich die Funktionen in dieser Benutzeroberfläche überhaupt nicht.
öffentliche Schnittstelle UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. Der Erbauer
Der Builder implementiert die Fieldable
Klasse. Es findet überhaupt keine Validierung statt, und um diese Tatsache zu betonen, sind alle seine Bereiche öffentlich und veränderlich. Obwohl diese öffentliche Zugänglichkeit keine Voraussetzung ist, bevorzuge und empfehle ich sie, da sie die Tatsache verstärkt, dass die Validierung erst nach dem ToBeBuilt
Aufruf des Konstruktors des Konstruktors erfolgt. Dies ist wichtig, da ein anderer Thread den Builder möglicherweise weiter manipulieren kann, bevor er an den ToBeBuilt
Konstruktor des Threads übergeben wird . Die einzige Möglichkeit, die Gültigkeit der Felder zu gewährleisten - vorausgesetzt, der Builder kann seinen Status nicht irgendwie "sperren" - besteht darin, dass die ToBeBuilt
Klasse die endgültige Prüfung durchführt.
Schließlich wird , wie mit der Fieldable
Schnittstelle, dokumentiere ich nichts von seinen Getter.
public class UserConfig_Cfg implementiert UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// selbst zurückkehrende Setter ... START
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
gib das zurück;
}
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
gib das zurück;
}
// selbst zurückkehrende Setter ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
return iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public UserConfig build () {
return (new UserConfig (this));
}
}
Instanziieren einer Klasse mit einem Blind Builder
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
Der einzige Unterschied ist " UserConfig_Cfg
" statt " UserConfig.Cfg
"
Anmerkungen
Nachteile:
- Blinde Bauherren können nicht auf private Mitglieder ihrer
ToBeBuilt
Klasse zugreifen.
- Sie sind ausführlicher, da jetzt sowohl im Builder als auch in der Schnittstelle Getter erforderlich sind.
- Alles für eine Klasse ist nicht mehr nur an einem Ort .
Das Kompilieren eines Blind Builder ist einfach:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Die Fieldable
Schnittstelle ist völlig optional
Für eine ToBeBuilt
Klasse mit wenigen erforderlichen Feldern - wie diese UserConfig
Beispielklasse - könnte der Konstruktor einfach sein
public UserConfig (String s_name, int i_age, String s_favColor) {
Und beim Baumeister mit angerufen
public UserConfig build () {
return (new UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Oder sogar durch Eliminieren der Getter (im Builder) insgesamt:
return (neue UserConfig (sName, iAge, sFavoriteColor));
Durch die direkte Übergabe von Feldern ist die ToBeBuilt
Klasse genauso "blind" (ohne Kenntnis ihres Builders) wie bei der Fieldable
Schnittstelle. Für ToBeBuilt
Klassen, die "um ein Vielfaches erweitert und sub-erweitert" werden sollen (wie im Titel dieses Beitrags angegeben), erfordert jede Änderung in einem beliebigen Feld Änderungen in jeder Unterklasse, in jedem Builder und ToBeBuilt
Konstruktor. Wenn die Anzahl der Felder und Unterklassen zunimmt, ist dies unpraktisch zu pflegen.
(In der Tat ist die Verwendung eines Builders mit wenigen erforderlichen Feldern möglicherweise zu viel des Guten . Für Interessenten finden Sie hier eine Auswahl der größeren Fieldable-Schnittstellen in meiner persönlichen Bibliothek.)
Sekundärklassen im Unterpaket
Ich entscheide mich dafür, alle Builder und die Fieldable
Klassen für alle Blind Builder in einem Unterpaket ihrer ToBeBuilt
Klasse zu haben. Das Unterpaket heißt immer " z
". Dies verhindert, dass diese sekundären Klassen die JavaDoc-Paketliste überladen. Beispielsweise
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Validierungsbeispiel
Wie oben erwähnt, erfolgt die gesamte Validierung im ToBeBuilt
Konstruktor des Benutzers. Hier ist noch einmal der Konstruktor mit Beispielvalidierungscode:
public UserConfig (UserConfig_Fieldable uc_f) {
//Transfer
Versuchen {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// validieren (sollte die Muster wirklich vorkompilieren ...)
Versuchen {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") darf nicht leer sein und darf nur Ziffern und Unterstriche enthalten.");
}
} catch (NullPointerException rx) {
neue NullPointerException ("uc_f.getName ()") auslösen;
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") ist kleiner als Null.");
}
Versuchen {
if (! Pattern.compile ("(?: rot | blau | grün | pink)"). matcher (sFavColor) .matches ()) {
throw new IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") ist nicht rot, blau, grün oder pink.");
}
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Builder dokumentieren
Dieser Abschnitt gilt sowohl für Bloch Builder als auch für Blind Builder. Es wird gezeigt, wie ich die Klassen in diesem Entwurf dokumentiere und Setter (im Builder) und ihre Getter (in der ToBeBuilt
Klasse) direkt miteinander in Querverweis setzten - mit einem einzigen Mausklick und ohne dass der Benutzer wissen muss, wo diese Funktionen sind tatsächlich vorhanden - und ohne dass der Entwickler irgendetwas redundant dokumentieren muss.
Getter: ToBeBuilt
Nur in den Klassen
Getter werden nur in der ToBeBuilt
Klasse dokumentiert . Die entsprechenden Getter in den Klassen _Fieldable
und
_Cfg
werden ignoriert. Ich dokumentiere sie überhaupt nicht.
/ **
<P> Das Alter des Benutzers. </ P>
@return Ein Int, der das Alter des Benutzers angibt.
@see UserConfig_Cfg # age (int)
@see getName ()
** /
public int getAge () {
return iAge;
}
Der erste @see
ist ein Link zu seinem Setter, der sich in der Builder-Klasse befindet.
Setter: In der Builder-Klasse
Der Setter wird so dokumentiert, als ob er sich in der ToBeBuilt
Klasse befindet , und auch als ob er die Validierung durchführt (was wirklich vom ToBeBuilt
Konstruktor des Setters durchgeführt wird ). Das Sternchen (" *
") ist ein visueller Hinweis darauf, dass sich das Ziel des Links in einer anderen Klasse befindet.
/ **
<P> Stellen Sie das Alter des Benutzers ein. </ P>
@param i_age Darf nicht kleiner als Null sein. Holen Sie sich mit {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
gib das zurück;
}
Weitere Informationen
Alles zusammenfassen: Die vollständige Quelle des Blind Builder-Beispiels mit vollständiger Dokumentation
UserConfig.java
import java.util.regex.Pattern;
/ **
<P> Informationen zu einem Benutzer - <I> [Builder: UserConfig_Cfg] </ I> </ P>
<P> Die Validierung aller Felder erfolgt in diesem Klassenkonstruktor. Jede Validierungsanforderung ist jedoch nur in den Setterfunktionen des Builders dokumentiert. </ P>
<P> {@ code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </ P>
** /
öffentliche Klasse UserConfig {
public static final void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
System.out.println (uc);
}
private final String sName;
private final int iAge;
private final String sFavColor;
/ **
<P> Erstellen Sie eine neue Instanz. Hiermit werden alle Felder festgelegt und überprüft. </ P>
@param uc_f Darf nicht {@code null} sein.
** /
public UserConfig (UserConfig_Fieldable uc_f) {
//Transfer
Versuchen {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
//bestätigen
Versuchen {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") darf nicht leer sein und darf nur Ziffern und Unterstriche enthalten.");
}
} catch (NullPointerException rx) {
neue NullPointerException ("uc_f.getName ()") auslösen;
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") ist kleiner als Null.");
}
Versuchen {
if (! Pattern.compile ("(?: rot | blau | grün | pink)"). matcher (sFavColor) .matches ()) {
throw new IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") ist nicht rot, blau, grün oder pink.");
}
} catch (NullPointerException rx) {
werfen neue NullPointerException ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Der Name des Benutzers. </ P>
@return Eine nicht - {@ code null}, nicht leere Zeichenfolge.
@see UserConfig_Cfg # UserConfig_Cfg (String)
@see #getAge ()
@see #getFavoriteColor ()
** /
public String getName () {
return sName;
}
/ **
<P> Das Alter des Benutzers. </ P>
@return Eine Zahl größer als oder gleich Null.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public int getAge () {
return iAge;
}
/ **
<P> Die Lieblingsfarbe des Benutzers. </ P>
@return Eine nicht - {@ code null}, nicht leere Zeichenfolge.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public String toString () {
return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Erforderlich für den {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable) -Konstruktor}. </ P>
** /
öffentliche Schnittstelle UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
UserConfig_Cfg.java
import java.util.regex.Pattern;
/ **
<P> Builder für {@link UserConfig}. </ P>
<P> Die Überprüfung aller Felder erfolgt im <CODE> UserConfig </ CODE> -Konstruktor. Jede Validierungsanforderung ist jedoch nur in diesen Klasseneinstellungsfunktionen dokumentiert. </ P>
** /
public class UserConfig_Cfg implementiert UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
/ **
<P> Erstellen Sie eine neue Instanz mit dem Namen des Benutzers. </ P>
@param s_name Darf nicht {@code null} oder leer sein und darf nur Buchstaben, Ziffern und Unterstriche enthalten. Holen Sie sich mit {@code UserConfig # getName () getName ()} {@code ()} .
** /
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// selbst zurückkehrende Setter ... START
/ **
<P> Stellen Sie das Alter des Benutzers ein. </ P>
@param i_age Darf nicht kleiner als Null sein. Holen Sie sich mit {@code UserConfig # getName () getName ()} {@code ()} .
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
gib das zurück;
}
/ **
<P> Stellen Sie die Lieblingsfarbe des Benutzers ein. </ P>
@param s_color Muss {@code "red"}, {@code "blue"}, {@code green} oder {@code "hot pink"} sein. Holen Sie sich mit {@code UserConfig # getName () getName ()} {@code ()} *.
@see #age (int)
** /
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
gib das zurück;
}
// selbst zurückkehrende Setter ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
return iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
/ **
<P> Erstellen Sie die UserConfig wie konfiguriert. </ P>
@return <CODE> (new {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (this)) </ CODE>
** /
public UserConfig build () {
return (new UserConfig (this));
}
}
asImmutable
und fügen Sie es in dieReadableFoo
Benutzeroberfläche ein. [Unter Verwendung dieser Philosophie würde das Aufrufenbuild
eines unveränderlichen Objekts einfach einen Verweis auf dasselbe Objekt zurückgeben.]*_Fieldable
neue Getter erweitern und hinzufügen und die erweitern*_Cfg
und neue Setter hinzufügen, aber ich verstehe nicht, warum Sie vorhandene Getter und Setter reproduzieren müssen. Sie werden vererbt und müssen nicht neu erstellt werden, es sei denn, sie benötigen andere Funktionen.Ich denke, die Frage hier geht von vornherein von etwas aus, ohne zu beweisen, dass das Builder-Muster von Natur aus gut ist.
Ich denke, das Baumuster ist selten, wenn überhaupt, eine gute Idee.
Builder-Muster Zweck
Der Zweck des Builder-Musters besteht darin, zwei Regeln beizubehalten, die das Konsumieren Ihrer Klasse erleichtern:
Objekte sollten nicht in inkonsistenten / unbrauchbaren / ungültigen Zuständen konstruiert werden können.
Person
Objekt , ohne konstruiert sein , esId
in gefüllt, während alle Teile des Codes , dass die Verwendung dieses Objekt kann verlangen , dieId
nur mit der richtig ArbeitPerson
.Objektkonstruktoren sollten nicht zu viele Parameter benötigen .
Der Zweck des Builder-Musters ist also nicht umstritten. Ich denke, ein Großteil des Wunsches und der Verwendung davon basiert auf Analysen, die im Grunde genommen so weit gegangen sind: Wir wollen diese beiden Regeln, das gibt diese beiden Regeln - obwohl ich denke, es lohnt sich, andere Wege zu untersuchen, um diese beiden Regeln zu erreichen.
Warum sollte man sich andere Ansätze ansehen?
Ich denke, der Grund wird durch die Tatsache dieser Frage selbst gut gezeigt; Es gibt Komplexität und viel Zeremonie in den Strukturen, wenn das Baumuster auf sie angewendet wird. Diese Frage stellt sich die Frage, wie ein Teil dieser Komplexität gelöst werden kann, da dies, wie oft, zu einem Szenario führt, das sich seltsam verhält (vererbt). Diese Komplexität erhöht auch den Wartungsaufwand (das Hinzufügen, Ändern oder Entfernen von Eigenschaften ist weitaus komplexer als sonst).
Andere Ansätze
Welche Ansätze gibt es für Regel Nummer eins oben? Der Schlüssel, auf den sich diese Regel bezieht, besteht darin, dass ein Objekt beim Erstellen alle Informationen enthält, die es zum ordnungsgemäßen Funktionieren benötigt. Nach dem Erstellen können diese Informationen nicht mehr extern geändert werden (es handelt sich also um unveränderliche Informationen).
Eine Möglichkeit, einem Objekt bei der Konstruktion alle erforderlichen Informationen zu geben, besteht darin, dem Konstruktor einfach Parameter hinzuzufügen. Wenn diese Informationen vom Konstruktor angefordert werden, können Sie dieses Objekt ohne diese Informationen nicht erstellen. Daher wird es in einen gültigen Zustand versetzt. Aber was ist, wenn das Objekt viele Informationen benötigt, um gültig zu sein? Oh, verdammt, wenn das der Fall wäre, würde dieser Ansatz gegen Regel Nr. 2 verstoßen .
Ok, was gibt es sonst noch? Nun, Sie können einfach alle Informationen, die für einen konsistenten Zustand Ihres Objekts erforderlich sind, in ein anderes Objekt bündeln, das zur Konstruktionszeit erfasst wird. Ihr Code oben anstelle eines Builder-Musters wäre dann:
Dies unterscheidet sich nicht wesentlich vom Builder-Muster, obwohl es etwas einfacher ist, und vor allem erfüllen wir jetzt Regel 1 und Regel 2 .
Warum also nicht das gewisse Extra dazugeben und es zu einem vollwertigen Builder machen? Es ist einfach unnötig . Ich habe beide Zwecke des Builder-Musters in diesem Ansatz erfüllt, mit etwas etwas Einfacherem, leichter zu pflegendem und wiederverwendbarem . Das letzte Bit ist der Schlüssel. Dieses verwendete Beispiel ist imaginär und eignet sich nicht für semantische Zwecke in der Praxis. Lassen Sie uns also zeigen, wie dieser Ansatz zu einem wiederverwendbaren DTO und nicht zu einer einzelnen Zweckklasse führt .
Wenn Sie also zusammenhängende DTOs wie diese erstellen, können beide den Zweck des Builder-Musters einfacher und mit einem breiteren Nutzen erfüllen. Darüber hinaus löst dieser Ansatz die Vererbungskomplexität, zu der das Builder-Muster führt:
Möglicherweise ist das DTO nicht immer zusammenhängend, oder um die Gruppierung von Eigenschaften zusammenhängend zu machen, müssen sie auf mehrere DTOs aufgeteilt werden. Dies ist eigentlich kein Problem. Wenn für Ihr Objekt 18 Eigenschaften erforderlich sind und Sie mit diesen Eigenschaften drei zusammenhängende DTOs erstellen können, verfügen Sie über eine einfache Konstruktion, die den Zwecken des Erstellers entspricht. Wenn Sie keine zusammenhängenden Gruppierungen finden können, kann dies ein Zeichen dafür sein, dass Ihre Objekte nicht zusammenhängend sind, wenn sie Eigenschaften aufweisen, die nicht in Beziehung zueinander stehen. Aber selbst dann ist es aufgrund der einfacheren Implementierung und dem Plus immer noch vorzuziehen, ein einziges nicht zusammenhängendes DTO zu erstellen Beheben Sie Ihr Vererbungsproblem.
So verbessern Sie das Builder-Muster
Ok, also, Sie haben ein Problem und suchen nach einem Design-Ansatz, um dieses Problem zu lösen. Mein Vorschlag: Das Erben von Klassen kann einfach eine verschachtelte Klasse haben, die von der Builder-Klasse der Superklasse erbt. Die erbende Klasse hat also im Grunde die gleiche Struktur wie die Superklasse und ein Builder-Muster, das mit den zusätzlichen Funktionen genau gleich funktionieren sollte für die zusätzlichen Eigenschaften der Unterklasse.
Wenn es eine gute Idee ist
Abgesehen davon hat das Baumuster eine Nische . Wir alle wissen es, weil wir alle diesen bestimmten Erbauer an der einen oder anderen Stelle gelernt haben:
StringBuilder
- Hier ist der Zweck keine einfache Konstruktion, da Strings nicht einfacher zu konstruieren und zu verketten sind usw. Dies ist ein großartiger Builder, weil er einen Leistungsvorteil hat .Der Leistungsvorteil ist also: Sie haben eine Reihe von Objekten, die unveränderlichen Typs sind, und Sie müssen sie auf ein Objekt eines unveränderlichen Typs reduzieren. Wenn Sie es inkrementell tun, werden hier viele Zwischenobjekte erstellt, so dass es weitaus performanter und idealer ist, alles auf einmal zu tun.
Ich denke, der Schlüssel zu einer guten Idee liegt in der Problemdomäne von
StringBuilder
: Mehrere Instanzen unveränderlicher Typen müssen in eine einzige Instanz eines unveränderlichen Typs umgewandelt werden .quelle
fooBuilder.withBar(2).withBang("Hello").withBaz(someComplexObject).build()
Bietet eine prägnante API zum Erstellen von Foos und kann die eigentliche Fehlerprüfung im Builder selbst anbieten. Ohne den Builder muss das Objekt selbst seine Eingaben überprüfen, was bedeutet, dass es uns nicht besser geht als früher.Fieldable
Parameter akzeptiert . Ich würde diese Validierungsfunktion vomToBeBuilt
Konstruktor aus aufrufen , aber sie könnte von jedem Ort aus von jedem aufgerufen werden. Dadurch wird das Potenzial für redundanten Code beseitigt, ohne eine bestimmte Implementierung zu erzwingen. (Und es gibt nichts , was Sie daran zu hindern, in den einzelnen Feldern der Validierung Funktion übergeben, wenn Sie das nicht tunFieldable
Konzept - aber jetzt wäre es zumindest drei Orte , in denen die Feldliste hätte beibehalten werden.)