Ho creato quello che, per me, è un grande miglioramento rispetto al Builder Pattern di Josh Bloch. Per non dire in alcun modo che è "migliore", solo che in una situazione molto specifica , offre alcuni vantaggi - il più grande è che disaccoppia il costruttore dalla sua classe da costruire.
Ho ampiamente documentato questa alternativa di seguito, che chiamo Blind Builder Pattern.
Motivo di progettazione: Blind Builder
In alternativa a Joshua Bloch's Builder Pattern (elemento 2 in Effective Java, 2a edizione), ho creato quello che chiamo "Blind Builder Pattern", che condivide molti dei vantaggi di Bloch Builder e, a parte un singolo personaggio, è usato esattamente allo stesso modo. I costruttori ciechi hanno il vantaggio di
- disaccoppiando il costruttore dalla sua classe chiusa, eliminando una dipendenza circolare,
- riduce notevolmente la dimensione del codice sorgente di (ciò che non è più ) la classe che lo racchiude e
- consente
ToBeBuilt
di estendere la classe senza dover estendere il suo builder .
In questa documentazione, mi riferirò alla classe in costruzione come " ToBeBuilt
" classe.
Una classe implementata con un Bloch Builder
Un Bloch Builder è un public static class
contenuto all'interno della classe che costruisce. Un esempio:
UserConfig di classe pubblica {
stringa finale privata sName;
int finale privato;
String finale privata sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//trasferimento
provare {
sName = uc_c.sName;
} catch (NullPointerException rx) {
lancio nuovo NullPointerException ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// CONVALIDA QUI TUTTI I CAMPI
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
classe statica pubblica Cfg {
private String sName;
private int iAge;
String privata sFavColor;
public Cfg (String s_name) {
sName = s_name;
}
// setter con ritorno automatico ... INIZIA
età Cfg pubblica (int i_age) {
iAge = i_age;
restituire questo;
}
public Cfg favoriteColor (String s_color) {
sFavColor = s_color;
restituire questo;
}
// setter con ritorno automatico ... END
public UserConfig build () {
return (nuovo UserConfig (this));
}
}
//builder...END
}
Creare un'istanza di una classe con un Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
La stessa classe, implementata come Blind Builder
Ci sono tre parti in un Blind Builder, ognuna delle quali si trova in un file di codice sorgente separato:
- La
ToBeBuilt
classe (in questo esempio UserConfig
:)
- La sua "
Fieldable
" interfaccia
- Il costruttore
1. La classe da costruire
La classe da costruire accetta la sua Fieldable
interfaccia come unico parametro costruttore. Il costruttore imposta da esso tutti i campi interni e convalida ciascuno di essi. Ancora più importante, questa ToBeBuilt
classe non ha conoscenza del suo costruttore.
UserConfig di classe pubblica {
stringa finale privata sName;
int finale privato;
String finale privata sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//trasferimento
provare {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
lancio nuovo NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// CONVALIDA QUI TUTTI I CAMPI
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Come notato da un commentatore intelligente (che inspiegabilmente cancellato la loro risposta), se la ToBeBuilt
classe implementa anche la sua Fieldable
, il suo costruttore uno-e-solo può essere usato sia come suo primario e costruttore di copia (uno svantaggio è che i campi siano sempre convalidati, anche se è noto che i campi nell'originale ToBeBuilt
sono validi).
2. L' Fieldable
interfaccia " "
L'interfaccia fieldable è il "ponte" tra la ToBeBuilt
classe e il suo builder, che definisce tutti i campi necessari per costruire l'oggetto. Questa interfaccia è richiesta dal ToBeBuilt
costruttore delle classi ed è implementata dal builder. Poiché questa interfaccia può essere implementata da classi diverse dal builder, qualsiasi classe può facilmente creare un'istanza della ToBeBuilt
classe, senza essere costretta a usare il suo builder. Questo rende anche più facile estendere la ToBeBuilt
classe, quando l'estensione del suo builder non è desiderabile o necessaria.
Come descritto in una sezione seguente, non documento affatto le funzioni di questa interfaccia.
interfaccia pubblica UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. Il costruttore
Il costruttore implementa la Fieldable
classe. Non convalida affatto e, per sottolineare questo fatto, tutti i suoi campi sono pubblici e mutevoli. Sebbene questa accessibilità pubblica non sia un requisito, la preferisco e la raccomando, poiché rafforza il fatto che la convalida non ha luogo finché non ToBeBuilt
viene chiamato il costruttore. Questo è importante, perché è possibile che un altro thread manipoli ulteriormente il builder, prima che venga passato al ToBeBuilt
costruttore del. L'unico modo per garantire la validità dei campi - supponendo che il costruttore non possa in qualche modo "bloccare" il suo stato - è che la ToBeBuilt
classe esegua il controllo finale.
Infine, come con l' Fieldable
interfaccia, non documento nessuno dei suoi getter.
classe pubblica UserConfig_Cfg implementa UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// setter con ritorno automatico ... INIZIA
età UserConfig_Cfg pubblica (int i_age) {
iAge = i_age;
restituire questo;
}
publicConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
restituire questo;
}
// setter con ritorno automatico ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
restituire iAge;
}
public String getFavoriteColor () {
restituisce sFavColor;
}
//getters...END
public UserConfig build () {
return (nuovo UserConfig (this));
}
}
Creare un'istanza di una classe con un Blind Builder
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
L'unica differenza è " UserConfig_Cfg
" anziché " UserConfig.Cfg
"
Gli appunti
svantaggi:
- I Blind Builders non possono accedere ai membri privati della sua
ToBeBuilt
classe,
- Sono più dettagliati, poiché i getter sono ora richiesti sia nel builder che nell'interfaccia.
- Tutto per una singola classe non è più in un solo posto .
Compilare un Blind Builder è semplice:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
L' Fieldable
interfaccia è completamente opzionale
Per una ToBeBuilt
classe con pochi campi obbligatori - come questa UserConfig
classe di esempio, il costruttore potrebbe semplicemente essere
public UserConfig (String s_name, int i_age, String s_favColor) {
E chiamato il costruttore con
public UserConfig build () {
return (new UserConfig (getName (), getAge (), getFavoriteColor ()));
}
O anche eliminando del tutto i getter (nel costruttore):
return (nuovo UserConfig (sName, iAge, sFavoriteColor));
Passando direttamente i campi, la ToBeBuilt
classe è altrettanto "cieca" (ignara del suo builder) come lo è con l' Fieldable
interfaccia. Tuttavia, per le ToBeBuilt
classi che e devono essere "estese e sub-estese molte volte" (che si trova nel titolo di questo post), qualsiasi modifica a qualsiasi campo richiede modifiche in ogni sottoclasse, in ogni costruttore e ToBeBuilt
costruttore. All'aumentare del numero di campi e sottoclassi, ciò diventa impraticabile da mantenere.
(In effetti, con pochi campi necessari, l'uso di un builder potrebbe essere eccessivo. Per chi è interessato, ecco un campionamento di alcune delle più grandi interfacce Fieldable nella mia biblioteca personale.)
Classi secondarie nel sotto-pacchetto
Ho scelto di avere tutti i builder e le Fieldable
classi, per tutti i Blind Builders, in un sotto-pacchetto della loro ToBeBuilt
classe. Il sotto-pacchetto è sempre chiamato " z
". Ciò impedisce a queste classi secondarie di ingombrare l'elenco dei pacchetti JavaDoc. Per esempio
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Esempio di convalida
Come accennato in precedenza, tutta la convalida si verifica nel ToBeBuilt
costruttore di. Ecco di nuovo il costruttore con un esempio di codice di convalida:
public UserConfig (UserConfig_Fieldable uc_f) {
//trasferimento
provare {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
lancio nuovo NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// validate (dovrebbe davvero pre-compilare i pattern ...)
provare {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") potrebbe non essere vuoto e deve contenere solo lettere, cifre e caratteri di sottolineatura.");
}
} catch (NullPointerException rx) {
throw new NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") è inferiore a zero.");
}
provare {
if (! Pattern.compile ("(?: red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
lanciare nuovo IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") non è rosso, blu, verde o rosa caldo.");
}
} catch (NullPointerException rx) {
lancia nuovo NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Costruttori di documentazione
Questa sezione è applicabile sia a Bloch Builders che a Blind Builders. Dimostra come documentare le classi in questo progetto, creando setter (nel costruttore) e loro getter (nella ToBeBuilt
classe) con riferimenti incrociati direttamente tra loro - con un solo clic del mouse e senza che l'utente debba sapere dove tali funzioni risiedono effettivamente e senza che lo sviluppatore debba documentare nulla in modo ridondante.
Getters: ToBeBuilt
solo nelle classi
I getter sono documentati solo nella ToBeBuilt
classe. I getter equivalenti sia nelle classi _Fieldable
che nelle
_Cfg
classi vengono ignorati. Non li documento affatto.
/ **
<P> L'età dell'utente. </P>
@return Un int che rappresenta l'età dell'utente.
@vedi UserConfig_Cfg # age (int)
@vedi getName ()
** /
public int getAge () {
restituire iAge;
}
Il primo @see
è un link al suo setter, che è nella classe builder.
Incastonatori: nella classe costruttore
Il setter è documentato come se fosse in ToBeBuilt
classe , e anche come se si fa la convalida (che in realtà è fatto da parte del ToBeBuilt
's costruttore). L'asterisco (" *
") è un indizio visivo che indica che la destinazione del collegamento è in un'altra classe.
/ **
<P> Imposta l'età dell'utente. </P>
@param i_age Potrebbe non essere inferiore a zero. Ottieni con {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (String)
** /
età UserConfig_Cfg pubblica (int i_age) {
iAge = i_age;
restituire questo;
}
Ulteriori informazioni
Mettere tutto insieme: la fonte completa dell'esempio di Blind Builder, con documentazione completa
UserConfig.java
import java.util.regex.Pattern;
/ **
<P> Informazioni su un utente - <I> [builder: UserConfig_Cfg] </I> </P>
<P> La convalida di tutti i campi si verifica in questo costruttore di classi. Tuttavia, ogni requisito di convalida è un documento solo nelle funzioni setter del builder. </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
UserConfig di classe pubblica {
public static final void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
System.out.println (UC);
}
stringa finale privata sName;
int finale privato;
String finale privata sFavColor;
/ **
<P> Crea una nuova istanza. Questo imposta e convalida tutti i campi. </P>
@param uc_f Potrebbe non essere {@code null}.
** /
public UserConfig (UserConfig_Fieldable uc_f) {
//trasferimento
provare {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
lancio nuovo NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
//convalidare
provare {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") potrebbe non essere vuoto e deve contenere solo lettere, cifre e caratteri di sottolineatura.");
}
} catch (NullPointerException rx) {
throw new NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") è inferiore a zero.");
}
provare {
if (! Pattern.compile ("(?: red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
lanciare nuovo IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") non è rosso, blu, verde o rosa caldo.");
}
} catch (NullPointerException rx) {
lancia nuovo NullPointerException ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Il nome dell'utente. </P>
@return Una stringa non - {@ code null}, non vuota.
@see UserConfig_Cfg # UserConfig_Cfg (String)
@see #getAge ()
@see #getFavoriteColor ()
** /
public String getName () {
return sName;
}
/ **
<P> L'età dell'utente. </P>
@return Un numero maggiore o uguale a zero.
@vedi UserConfig_Cfg # age (int)
@vedi #getName ()
** /
public int getAge () {
restituire iAge;
}
/ **
<P> Il colore preferito dell'utente. </P>
@return Una stringa non - {@ code null}, non vuota.
@vedi UserConfig_Cfg # age (int)
@vedi #getName ()
** /
public String getFavoriteColor () {
restituisce sFavColor;
}
//getters...END
public String toString () {
return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Richiesto dal costruttore {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable)}. </P>
** /
interfaccia pubblica UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
UserConfig_Cfg.java
import java.util.regex.Pattern;
/ **
<P> Builder per {@link UserConfig}. </P>
<P> La convalida di tutti i campi si verifica nel costruttore <CODE> UserConfig </CODE>. Tuttavia, ogni requisito di convalida è documentato solo nelle funzioni di setter di questa classe. </P>
** /
classe pubblica UserConfig_Cfg implementa UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
/ **
<P> Crea una nuova istanza con il nome dell'utente. </P>
@param s_name Non può essere {@code null} o vuoto e deve contenere solo lettere, cifre e caratteri di sottolineatura. Ottieni con {@code UserConfig # getName () getName ()} {@ code ()} .
** /
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// setter con ritorno automatico ... INIZIA
/ **
<P> Imposta l'età dell'utente. </P>
@param i_age Potrebbe non essere inferiore a zero. Ottieni con {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (String)
** /
età UserConfig_Cfg pubblica (int i_age) {
iAge = i_age;
restituire questo;
}
/ **
<P> Imposta il colore preferito dell'utente. </P>
@param s_color Deve essere {@code "red"}, {@code "blue"}, {@code green} o {@code "hot pink"}. Ottieni con {@code UserConfig # getName () getName ()} {@ code ()} *.
@see #age (int)
** /
publicConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
restituire questo;
}
// setter con ritorno automatico ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
restituire iAge;
}
public String getFavoriteColor () {
restituisce sFavColor;
}
//getters...END
/ **
<P> Crea UserConfig, come configurato. </P>
@return <CODICE> (nuovo {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (questo)) </CODE>
** /
public UserConfig build () {
return (nuovo UserConfig (this));
}
}