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
ToBeBuilt
klasy bez konieczności rozszerzania jej konstruktora .
W tej dokumentacji będę się odnosił do budowanej klasy jako do ToBeBuilt
klasy „ ”.
Klasa zaimplementowana w programie Bloch Builder
Konstruktor Bloch jest public static class
zawarty 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:
ToBeBuilt
Klasy (w tym przykładzie: UserConfig
)
- Jego
Fieldable
interfejs „ ”
- Budowniczy
1. Klasa do zbudowania
Klasa, która ma zostać zbudowana, przyjmuje swój Fieldable
interfejs jako jedyny parametr konstruktora. Konstruktor ustawia z niego wszystkie pola wewnętrzne i sprawdza każde z nich. Co najważniejsze, ta ToBeBuilt
klasa 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 ToBeBuilt
klasa 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 ToBeBuilt
są prawidłowe).
2. Interfejs „ Fieldable
”
Interfejs polowy jest „pomostem” między ToBeBuilt
klasą a jej konstruktorem, definiując wszystkie pola niezbędne do zbudowania obiektu. Ten interfejs jest wymagany przez ToBeBuilt
konstruktora 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ę ToBeBuilt
klasy, bez konieczności korzystania z jej konstruktora. Ułatwia to także rozszerzanie ToBeBuilt
klasy, 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 Fieldable
klasę. 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 ToBeBuilt
zostanie 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 ToBeBuilt
sprawdzenie klasy przez klasę.
Wreszcie, podobnie jak w przypadku Fieldable
interfejsu, 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
ToBeBuilt
klasy,
- 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
Fieldable
Interfejs jest całkowicie opcjonalne
W przypadku ToBeBuilt
klasy z kilkoma wymaganymi polami - takiej jak UserConfig
przykł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 ToBeBuilt
klasa jest tak samo „ślepa” (nieświadoma swojego konstruktora), jak w przypadku Fieldable
interfejsu. Jednak w przypadku ToBeBuilt
klas, 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 ToBeBuilt
konstruktorze. 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 Fieldable
klas, dla wszystkich konstruktorów niewidomych, w paczce ich ToBeBuilt
klasy. 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 ToBeBuilt
konstruktorze. 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 ToBeBuilt
klasie) 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: ToBeBuilt
Tylko w klasach
Gettery są dokumentowane tylko w ToBeBuilt
klasie. Odpowiedniki pobierające zarówno w klasach, jak _Fieldable
i
_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 @see
to link do jego setera, który należy do klasy konstruktora.
Settery: w klasie budowniczej
Setter jest udokumentowany tak, jakby to w ToBeBuilt
klasie , 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));
}
}