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));
}
}
asImmutable
i umieść wReadableFoo
interfejsie [używając tej filozofii, wywołaniebuild
niezmiennego obiektu po prostu zwróci odwołanie do tego samego obiektu].*_Fieldable
i dodać do niego nowe programy pobierające oraz rozszerzyć i dodać nowe programy*_Cfg
ustawiające, ale nie rozumiem, dlaczego musisz odtwarzać istniejące programy pobierające i ustawiające. Są dziedziczone i jeśli nie potrzebują innej funkcjonalności, nie trzeba ich ponownie tworzyć.Myślę, że pytanie tutaj zakłada coś od samego początku, nie próbując tego udowodnić, że wzorzec konstruktora jest z natury dobry.
tl; dr Myślę, że wzór konstruktora rzadko, jeśli w ogóle, jest dobrym pomysłem.
Cel wzorca budowniczego
Celem wzorca konstruktora jest utrzymanie dwóch reguł, które ułatwią konsumpcję klasy:
Obiekty nie powinny mieć możliwości konstruowania w stanach niespójnych / nieużytecznych / nieprawidłowych.
Person
obiekt może być skonstruowana bez konieczności toId
wypełnione, a wszystkie fragmenty kodu, które używają tego obiektu może wymagać sięId
tylko do prawidłowej pracy zPerson
.Konstruktory obiektów nie powinny wymagać zbyt wielu parametrów .
Cel wzorca konstruktora jest więc nie kontrowersyjny. Myślę, że wiele z tego pragnienia i jego wykorzystania opiera się na analizie, która posunęła się zasadniczo tak daleko: chcemy tych dwóch reguł, to daje te dwie reguły - choć uważam, że warto zbadać inne sposoby realizacji tych dwóch zasad.
Po co męczyć się, patrząc na inne podejścia?
Myślę, że powód dobrze pokazuje sam fakt tego pytania; zastosowanie struktur budowniczych powoduje złożoność i wiele ceremonii. To pytanie pyta, jak rozwiązać część tej złożoności, ponieważ jak często złożoność, tworzy scenariusz, który zachowuje się dziwnie (dziedziczenie). Ta złożoność zwiększa również koszty utrzymania (dodawanie, zmienianie lub usuwanie właściwości jest znacznie bardziej złożone niż w innych przypadkach).
Inne podejścia
Jakie są zatem podejścia do powyższej zasady nr 1? Kluczem, do którego odnosi się ta reguła, jest to, że po zbudowaniu obiekt ma wszystkie informacje potrzebne do prawidłowego funkcjonowania - a po zbudowaniu informacji tych nie można zmienić zewnętrznie (więc są to informacje niezmienne).
Jednym ze sposobów przekazania wszystkich niezbędnych informacji obiektowi podczas budowy jest po prostu dodanie parametrów do konstruktora. Jeśli konstruktor zażąda tych informacji, nie będziesz w stanie zbudować tego obiektu bez wszystkich tych informacji, dlatego zostanie on skonstruowany w prawidłowy stan. Ale co jeśli obiekt wymaga dużej ilości informacji, aby był ważny? O cholera, jeśli tak jest, to podejście złamałoby zasadę nr 2 powyżej .
Ok, co jeszcze tam jest? Cóż, możesz po prostu wziąć wszystkie te informacje, które są niezbędne, aby twój obiekt był w spójnym stanie, i połączyć je w inny obiekt, który jest pobierany podczas budowy. Twój kod powyżej zamiast wzorca budowniczego byłby wtedy:
Nie różni się to zbytnio od wzorca konstruktora, choć jest nieco prostsze i, co najważniejsze, spełniamy teraz zasadę nr 1 i zasadę nr 2 .
Dlaczego więc nie pójść trochę dalej i zrobić z tego pełny builder? To po prostu niepotrzebne . W tym podejściu spełniłem oba cele wzorca konstruktora - coś nieco prostszego, łatwiejszego w utrzymaniu i wielokrotnego użytku . Ta ostatnia kwestia jest kluczowa, ponieważ ten przykład jest wymyślony i nie nadaje się do celu semantycznego w świecie rzeczywistym, więc pokażmy, w jaki sposób takie podejście prowadzi do wielokrotnego użytku DTO, a nie do pojedynczej klasy celu .
Więc kiedy budujesz spójne DTO w ten sposób, mogą one zarówno spełniać cel wzorca konstruktora, prościej i przy większej wartości / użyteczności. Ponadto takie podejście rozwiązuje złożoność dziedziczenia, jaką daje wzorzec konstruktora:
Może się okazać, że DTO nie zawsze jest spójne lub aby grupy właściwości były spójne, trzeba je rozbić na wiele DTO - to nie jest tak naprawdę problem. Jeśli twój obiekt wymaga 18 właściwości i możesz zrobić 3 spójne DTO z tymi właściwościami, masz prostą konstrukcję, która spełnia cele konstruktorów, a następnie niektóre. Jeśli nie możesz wymyślić spójnych grup, może to być znak, że twoje obiekty nie są spójne, jeśli mają właściwości, które są tak całkowicie niezwiązane - ale nawet wtedy tworzenie pojedynczego niespójnego DTO jest nadal preferowane ze względu na prostszą implementację plus rozwiązanie problemu z dziedziczeniem.
Jak poprawić wzorzec konstruktora
Ok, więc odsuńmy na bok wszystkie felgi, masz problem i szukasz rozwiązania projektowego, aby je rozwiązać. Moja sugestia: klasy dziedziczące mogą mieć po prostu klasę zagnieżdżoną, która dziedziczy z klasy konstruktora superklasy, więc klasa dziedzicząca ma zasadniczo taką samą strukturę jak klasa super i ma wzorzec konstruktora, który powinien działać dokładnie tak samo z dodatkowymi funkcjami dla dodatkowych właściwości podklasy.
Kiedy jest to dobry pomysł
Odkładając na bok, wzorzec budowniczego ma niszę . Wszyscy to wiemy, ponieważ wszyscy nauczyliśmy się tego konkretnego konstruktora w tym czy innym momencie:
StringBuilder
- tutaj celem nie jest prosta konstrukcja, ponieważ łańcuchy nie mogą być łatwiejsze do skonstruowania i połączenia itp. Jest to świetny konstruktor, ponieważ zapewnia korzyści w zakresie wydajności .Korzyści płynące z wydajności są zatem następujące: masz mnóstwo obiektów, są one niezmiennego typu, musisz zwinąć je do jednego obiektu niezmiennego typu. Jeśli robisz to stopniowo, utworzysz tutaj wiele obiektów pośrednich, więc robienie tego wszystkiego naraz jest o wiele bardziej wydajne i idealne.
Myślę więc, że kluczem do tego, kiedy jest to dobry pomysł, jest dziedzina problemowa
StringBuilder
: Konieczność przekształcenia wielu instancji typów niezmiennych w jedną instancję typu niezmiennego .źródło
fooBuilder.withBar(2).withBang("Hello").withBaz(someComplexObject).build()
oferuje zwięzły interfejs API do tworzenia foos i może zaoferować faktyczne sprawdzanie błędów w samym kreatorze. Bez konstruktora sam obiekt musi sprawdzić swoje dane wejściowe, co oznacza, że nie jesteśmy w lepszej sytuacji niż kiedyś.Fieldable
parametr. Ja nazwałbym tę funkcję walidacji zToBeBuilt
konstruktora, ale może być wywołana przez cokolwiek, z dowolnego miejsca. Eliminuje to potencjał nadmiarowego kodu, bez wymuszania konkretnej implementacji. (I nic nie stoi na przeszkodzie, abyś przeszedł w poszczególnych polach do funkcji sprawdzania poprawności, jeśli nie podoba ci się taFieldable
koncepcja - ale teraz byłyby co najmniej trzy miejsca, w których lista pól musiałaby być utrzymywana.)