Stworzyłem coś, co jest dla mnie dużym ulepszeniem w stosunku do wzorca budowniczego Josha Blocha. Nie mówiąc w żaden sposób, że jest „lepszy”, tylko że w bardzo specyficznej sytuacji ma pewne zalety - największą jest to, że oddziela budowniczego od klasy, która ma być budowana.
Dokładnie udokumentowałem tę alternatywę poniżej, którą nazywam Wzorcem Niewidomych.
Wzorzec projektu: niewidomy budowniczy
Jako alternatywa dla Wzorca budowniczego Joshua Blocha (pozycja 2 w Efektywnej Javie, wydanie 2) stworzyłem coś, co nazywam „Wzorcem niewidomych”, który ma wiele zalet Budowniczego Blocha i, oprócz jednej postaci, jest używany dokładnie w ten sam sposób. Niewidomi budowniczowie mają tę przewagę
- oddzielenie konstruktora od otaczającej go klasy, eliminując cykliczną zależność,
- znacznie zmniejsza rozmiar kodu źródłowego (co już nie jest ) klasy zamykającej, oraz
- umożliwia rozszerzenie
ToBeBuiltklasy bez konieczności rozszerzania jej konstruktora .
W tej dokumentacji będę się odnosił do budowanej klasy jako do ToBeBuiltklasy „ ”.
Klasa zaimplementowana w programie Bloch Builder
Konstruktor Bloch jest public static classzawarty w klasie, którą buduje. Przykład:
UserConfig klasy publicznej {
prywatny końcowy ciąg sName;
prywatny finał iAge;
prywatny końcowy ciąg sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//transfer
próbować {
sName = uc_c.sName;
} catch (NullPointerException rx) {
zgłosić nowy wyjątek NullPointerException („uc_c”);
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// WALIDUJ WSZYSTKIE POLA TUTAJ
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
publiczna klasa statyczna Cfg {
prywatny ciąg sName;
prywatny int iAge;
private String sFavColor;
public Cfg (String s_name) {
sName = nazwa_s;
}
// sety powracające ... START
publiczny wiek Cfg (int i_age) {
iAge = i_age;
zwróć to;
}
public Cfg favouriteColor (String s_color) {
sFavColor = s_color;
zwróć to;
}
// ustawiacze samowracające ... KONIEC
public UserConfig build () {
return (new UserConfig (this));
}
}
//builder...END
}
Tworzenie instancji klasy za pomocą programu Bloch Builder
UserConfig uc = new UserConfig.Cfg („Kermit”). Age (50). FavoriteColor („zielony”). Build ();
Ta sama klasa, zaimplementowana jako Blind Builder
Kreator niewidomych składa się z trzech części, z których każda znajduje się w osobnym pliku kodu źródłowego:
ToBeBuiltKlasy (w tym przykładzie: UserConfig)
- Jego
Fieldableinterfejs „ ”
- Budowniczy
1. Klasa do zbudowania
Klasa, która ma zostać zbudowana, przyjmuje swój Fieldableinterfejs jako jedyny parametr konstruktora. Konstruktor ustawia z niego wszystkie pola wewnętrzne i sprawdza każde z nich. Co najważniejsze, ta ToBeBuiltklasa nie ma wiedzy o swoim konstruktorze.
UserConfig klasy publicznej {
prywatny końcowy ciąg sName;
prywatny finał iAge;
prywatny końcowy ciąg sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//transfer
próbować {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException („uc_f”);
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// WALIDUJ WSZYSTKIE POLA TUTAJ
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Jak zauważył jeden inteligentny komentator (który w niewytłumaczalny sposób usunął swoją odpowiedź), jeśli ToBeBuiltklasa również ją implementuje Fieldable, jej jedyny w swoim rodzaju konstruktor może być używany zarówno jako konstruktor główny, jak i konstruktor kopiujący (wadą jest to, że pola są zawsze sprawdzane, chociaż wiadomo, że pola w oryginale ToBeBuiltsą prawidłowe).
2. Interfejs „ Fieldable”
Interfejs polowy jest „pomostem” między ToBeBuiltklasą a jej konstruktorem, definiując wszystkie pola niezbędne do zbudowania obiektu. Ten interfejs jest wymagany przez ToBeBuiltkonstruktora klas i jest implementowany przez konstruktora. Ponieważ interfejs ten może być zaimplementowany przez klasy inne niż konstruktor, każda klasa może łatwo utworzyć instancję ToBeBuiltklasy, bez konieczności korzystania z jej konstruktora. Ułatwia to także rozszerzanie ToBeBuiltklasy, gdy rozszerzanie jej konstruktora nie jest pożądane ani konieczne.
Jak opisano w poniższej sekcji, w ogóle nie dokumentuję funkcji tego interfejsu.
interfejs publiczny UserConfig_Fieldable {
Ciąg getName ();
int getAge ();
Ciąg getFavoriteColor ();
}
3. Konstruktor
Konstruktor implementuje Fieldableklasę. W ogóle nie sprawdza poprawności i aby podkreślić ten fakt, wszystkie jego pola są publiczne i można je modyfikować. Chociaż ta publiczna dostępność nie jest wymagana, wolę ją i polecam, ponieważ wzmacnia to fakt, że sprawdzanie poprawności nie następuje, dopóki nie ToBeBuiltzostanie wywołany konstruktor. Jest to ważne, dlatego, że jest możliwe na inny wątek manipulować wypełniacz ponadto, przed przekazaniem go w ToBeBuilt„s konstruktora. Jedynym sposobem na zagwarantowanie poprawności pól - zakładając, że konstruktor nie może w jakiś sposób „zablokować” swojego stanu - jest ToBeBuiltsprawdzenie klasy przez klasę.
Wreszcie, podobnie jak w przypadku Fieldableinterfejsu, nie dokumentuję żadnego z jego modułów pobierających.
klasa publiczna UserConfig_Cfg implementuje UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (String s_name) {
sName = nazwa_s;
}
// sety powracające ... START
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
zwróć to;
}
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
zwróć to;
}
// ustawiacze samowracające ... KONIEC
//getters...START
ciąg publiczny getName () {
return sName;
}
public int getAge () {
zwróć iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public UserConfig build () {
return (new UserConfig (this));
}
}
Tworzenie instancji klasy za pomocą programu Blind Builder
UserConfig uc = new UserConfig_Cfg („Kermit”). Age (50) .favoriteColor („zielony”). Build ();
Jedyną różnicą jest „ UserConfig_Cfg” zamiast „ UserConfig.Cfg”
Notatki
Niedogodności:
- Niewidomi budowniczowie nie mogą uzyskać dostępu do prywatnych członków swojej
ToBeBuiltklasy,
- Są bardziej gadatliwi, ponieważ narzędzia pobierające są teraz wymagane zarówno w kreatorze, jak iw interfejsie.
- Wszystko dla jednej klasy nie jest już w jednym miejscu .
Kompilowanie Blind Buildera jest proste:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
FieldableInterfejs jest całkowicie opcjonalne
W przypadku ToBeBuiltklasy z kilkoma wymaganymi polami - takiej jak UserConfigprzykładowa klasa, konstruktorem może być po prostu
public UserConfig (String s_name, int i_age, String s_favColor) {
I wezwał konstruktora z
public UserConfig build () {
return (new UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Lub nawet całkowicie eliminując pobierających (w konstruktorze):
return (new UserConfig (sName, iAge, sFavoriteColor));
Poprzez bezpośrednie przekazywanie pól ToBeBuiltklasa jest tak samo „ślepa” (nieświadoma swojego konstruktora), jak w przypadku Fieldableinterfejsu. Jednak w przypadku ToBeBuiltklas, które mają być „wielokrotnie rozszerzane i rozszerzane wielokrotnie” (co jest w tytule tego postu), wszelkie zmiany w dowolnym polu wymagają zmian w każdej podklasie, w każdym konstruktorze i ToBeBuiltkonstruktorze. Wraz ze wzrostem liczby pól i podklas staje się to niepraktyczne.
(Rzeczywiście, z kilkoma niezbędnymi polami, użycie konstruktora może być przesadą. Dla zainteresowanych, oto próbka niektórych z większych interfejsów Fieldable w mojej osobistej bibliotece.)
Klasy wtórne w paczce
Wybieram, aby mieć wszystkich konstruktorów i Fieldableklas, dla wszystkich konstruktorów niewidomych, w paczce ich ToBeBuiltklasy. Pakiet podrzędny ma zawsze nazwę „ z”. Zapobiega to zaśmiecaniu tych klas drugorzędnych listy pakietów JavaDoc. Na przykład
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Przykład walidacji
Jak wspomniano powyżej, wszystkie sprawdzanie poprawności odbywa się w ToBeBuiltkonstruktorze. Oto konstruktor ponownie z przykładowym kodem sprawdzającym:
public UserConfig (UserConfig_Fieldable uc_f) {
//transfer
próbować {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException („uc_f”);
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// walidacja (powinna naprawdę skompilować wzorce ...)
próbować {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
wrzuć nowy wyjątek IllegalArgumentException („uc_f.getName () (\” + sName + „\”) nie może być pusty i musi zawierać tylko litery, cyfry i znaki podkreślenia. ”);
}
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException („uc_f.getName ()”);
}
jeśli (iAge <0) {
wyrzuć nowy IllegalArgumentException („uc_f.getAge () („ + iAge + ”) jest mniejsza niż zero.”);
}
próbować {
if (! Pattern.compile ("(?: czerwony | niebieski | zielony | gorący różowy)"). matcher (sFavColor) .matches ()) {
wyrzuć nowy IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") nie jest czerwony, niebieski, zielony ani gorący różowy.");
}
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Dokumentowanie konstruktorów
Ta sekcja dotyczy zarówno konstruktorów Bloch, jak i konstruktorów niewidomych. Pokazuje, jak dokumentuję klasy w tym projekcie, czyniąc settery (w kreatorze) i ich gettery (w ToBeBuiltklasie) bezpośrednio ze sobą powiązane - za pomocą jednego kliknięcia myszy i bez potrzeby, aby użytkownik wiedział, gdzie funkcje te faktycznie znajdują się - i bez konieczności tworzenia przez program zbędnych dokumentów.
Getters: ToBeBuiltTylko w klasach
Gettery są dokumentowane tylko w ToBeBuiltklasie. Odpowiedniki pobierające zarówno w klasach, jak _Fieldablei_Cfg klasach są ignorowane. W ogóle ich nie dokumentuję.
/ **
<P> Wiek użytkownika. </P>
@return Int reprezentujący wiek użytkownika.
@see UserConfig_Cfg # age (int)
@ patrz getName ()
** /
public int getAge () {
zwróć iAge;
}
Pierwszy @seeto link do jego setera, który należy do klasy konstruktora.
Settery: w klasie budowniczej
Setter jest udokumentowany tak, jakby to w ToBeBuiltklasie , a także, jeśli to robi walidacji (która naprawdę jest wykonywana przez ToBeBuilt„s konstruktora). Gwiazdka („ *”) to wizualna wskazówka wskazująca, że cel łącza znajduje się w innej klasie.
/ **
<P> Ustaw wiek użytkownika. </P>
@param i_age Nie może być mniejsza niż zero. Uzyskaj za pomocą {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
zwróć to;
}
Dalsza informacja
Wszystko razem: pełne źródło przykładu Blind Buildera z pełną dokumentacją
UserConfig.java
import java.util.regex.Pattern;
/ **
<P> Informacje o użytkowniku - <I> [konstruktor: UserConfig_Cfg] </I> </P>
<P> Sprawdzanie poprawności wszystkich pól następuje w tym konstruktorze klas. Jednak każdy wymóg sprawdzania poprawności jest dokumentowany tylko w funkcjach programu budującego. </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
UserConfig klasy publicznej {
public static final void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg („Kermit”). Age (50) .favoriteColor („zielony”). Build ();
System.out.println (uc);
}
prywatny końcowy ciąg sName;
prywatny finał iAge;
prywatny końcowy ciąg sFavColor;
/ **
<P> Utwórz nową instancję. Spowoduje to ustawienie i sprawdzenie wszystkich pól. </P>
@param uc_f Może nie być {@code null}.
** /
public UserConfig (UserConfig_Fieldable uc_f) {
//transfer
próbować {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException („uc_f”);
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
//uprawomocnić
próbować {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
wrzuć nowy wyjątek IllegalArgumentException („uc_f.getName () (\” + sName + „\”) nie może być pusty i musi zawierać tylko litery, cyfry i znaki podkreślenia. ”);
}
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException („uc_f.getName ()”);
}
jeśli (iAge <0) {
wyrzuć nowy IllegalArgumentException („uc_f.getAge () („ + iAge + ”) jest mniejsza niż zero.”);
}
próbować {
if (! Pattern.compile ("(?: czerwony | niebieski | zielony | gorący różowy)"). matcher (sFavColor) .matches ()) {
wyrzuć nowy IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") nie jest czerwony, niebieski, zielony ani gorący różowy.");
}
} catch (NullPointerException rx) {
wrzuć nowy wyjątek NullPointerException ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Nazwa użytkownika. </P>
@return Nie - {@ kod null}, niepuste ciąg.
@see UserConfig_Cfg # UserConfig_Cfg (String)
@see #getAge ()
@see #getFavoriteColor ()
** /
ciąg publiczny getName () {
return sName;
}
/ **
<P> Wiek użytkownika. </P>
@return Liczba większa niż lub równa zero.
@see UserConfig_Cfg # age (int)
@ patrz #getName ()
** /
public int getAge () {
zwróć iAge;
}
/ **
<P> Ulubiony kolor użytkownika. </P>
@return Nie - {@ kod null}, niepuste ciąg.
@see UserConfig_Cfg # age (int)
@ patrz #getName ()
** /
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public String toString () {
return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Wymagane przez konstruktora {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable)}. </P>
** /
interfejs publiczny UserConfig_Fieldable {
Ciąg getName ();
int getAge ();
Ciąg getFavoriteColor ();
}
UserConfig_Cfg.java
import java.util.regex.Pattern;
/ **
<P> Kreator {@link UserConfig}. </P>
<P> Sprawdzanie poprawności wszystkich pól następuje w konstruktorze <CODE> UserConfig </CODE>. Jednak każde wymaganie sprawdzania poprawności jest dokumentem tylko w funkcjach ustawiania klas. </P>
** /
klasa publiczna UserConfig_Cfg implementuje UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
/ **
<P> Utwórz nową instancję z nazwą użytkownika. </P>
@param s_name Nie może być {@code null} ani pusty i musi zawierać tylko litery, cyfry i znaki podkreślenia. Uzyskaj za pomocą {@code UserConfig # getName () getName ()} {@ code ()} .
** /
public UserConfig_Cfg (String s_name) {
sName = nazwa_s;
}
// sety powracające ... START
/ **
<P> Ustaw wiek użytkownika. </P>
@param i_age Nie może być mniejsza niż zero. Uzyskaj za pomocą {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
zwróć to;
}
/ **
<P> Ustaw ulubiony kolor użytkownika. </P>
@param s_color Musi być {@code „czerwony”}, {@code „niebieski”}, {@code zielony} lub {@code „gorący różowy”}. Uzyskaj za pomocą {@code UserConfig # getName () getName ()} {@ code ()} *.
@see #age (int)
** /
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
zwróć to;
}
// ustawiacze samowracające ... KONIEC
//getters...START
ciąg publiczny getName () {
return sName;
}
public int getAge () {
zwróć iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
/ **
<P> Zbuduj UserConfig zgodnie z konfiguracją. </P>
@return <CODE> (nowy {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (this)) </CODE>
** /
public UserConfig build () {
return (new UserConfig (this));
}
}