Widziałem wiele implementacji wzorca Builder (głównie w Javie). Wszystkie mają klasę encji (powiedzmy Person
klasę) i klasę konstruktora PersonBuilder
. Konstruktor „układa” różne pola i zwraca new Person
argument z przekazanymi argumentami. Dlaczego jawnie potrzebujemy klasy konstruktora, zamiast umieszczać wszystkie metody konstruktora w Person
samej klasie?
Na przykład:
class Person {
private String name;
private Integer age;
public Person() {
}
Person withName(String name) {
this.name = name;
return this;
}
Person withAge(int age) {
this.age = age;
return this;
}
}
Mogę po prostu powiedzieć Person john = new Person().withName("John");
Dlaczego potrzeba PersonBuilder
zajęć?
Jedyną korzyścią, jaką widzę, jest to, że możemy zadeklarować Person
pola jako final
, zapewniając w ten sposób niezmienność.
java
design-patterns
builder-pattern
Boyan Kushlev
źródło
źródło
chainable setters
: DwithName
zwrócić kopię Osoby ze zmienionym tylko polem nazwiska. Innymi słowy,Person john = new Person().withName("John");
może działać, nawet jeśliPerson
jest niezmienny (i jest to powszechny wzorzec w programowaniu funkcjonalnym).void
metod. Na przykład, jeśliPerson
ma metodę, która wypisuje ich nazwę, nadal możesz połączyć ją w płynny interfejsperson.setName("Alice").sayName().setName("Bob").sayName()
. Nawiasem mówiąc, adnotuję te w JavaDoc z dokładnie twoją sugestią@return Fluent interface
- jest to ogólne i wystarczająco jasne, gdy stosuje się do dowolnej metody, która robi toreturn this
pod koniec jej wykonywania i jest dość jasne. Konstruktor będzie więc również płynnie posługiwał się interfejsem.Odpowiedzi:
Dzięki temu możesz być niezmienny ORAZ symulować jednocześnie nazwane parametry .
To trzyma twoje rękawiczki z dala od osoby, dopóki jej stan nie zostanie ustawiony, a po ustawieniu nie pozwoli ci go zmienić, ale każde pole jest wyraźnie oznaczone. Nie możesz tego zrobić za pomocą tylko jednej klasy w Javie.
Wygląda na to, że mówisz o Josh Blochs Builder Pattern . Nie należy tego mylić z wzorcem Gang czterech budowniczych . To są różne bestie. Oba rozwiązują problemy konstrukcyjne, ale na dość różne sposoby.
Oczywiście możesz zbudować obiekt bez użycia innej klasy. Ale wtedy musisz wybrać. Tracisz zdolność symulowania nazwanych parametrów w językach, które ich nie mają (np. Java) lub tracisz zdolność do niezmienności przez cały okres istnienia obiektów.
Niezmienny przykład nie ma nazw parametrów
Tutaj budujesz wszystko za pomocą jednego prostego konstruktora. Pozwoli ci to pozostać niezmiennym, ale stracisz symulację nazwanych parametrów. Trudno to odczytać przy wielu parametrach. Komputery nie dbają o to, ale jest to trudne dla ludzi.
Symulowany przykładowy parametr z tradycyjnymi ustawieniami. Niezmienne.
Tutaj budujesz wszystko za pomocą seterów i symulujesz nazwane parametry, ale nie jesteś już niezmienny. Każde użycie settera zmienia stan obiektu.
Dzięki dodaniu klasy możesz zrobić jedno i drugie.
Sprawdzanie poprawności można wykonać,
build()
jeśli wystarczający jest błąd czasu wykonania dla brakującego pola wieku. Możesz to uaktualnić i wymusićage()
wywołanie z błędem kompilatora. Po prostu nie z wzorem konstruktora Josh Bloch.Do tego potrzebny jest wewnętrzny język specyficzny dla domeny (iDSL).
Dzięki temu możesz żądać, aby zadzwonili
age()
iname()
przed połączeniembuild()
. Ale nie możesz tego zrobić po prostu wracając zathis
każdym razem. Każda zwracana rzecz zwraca inną rzecz, która zmusza cię do wezwania następnej rzeczy.Zastosowanie może wyglądać następująco:
Ale to:
powoduje błąd kompilatora, ponieważ
age()
można wywoływać tylko typ zwracany przezname()
.Te iDSL są niezwykle wydajne ( np. Strumienie JOOQ lub Java8 ) i są bardzo miłe w użyciu, szczególnie jeśli używasz IDE z uzupełnianiem kodu, ale ich konfiguracja wymaga sporo pracy. Polecam zapisywanie ich dla rzeczy, które będą miały sporo kodu źródłowego napisanego przeciwko nim.
źródło
AnonymousPersonBuilder
własny zestaw reguł.Dlaczego warto korzystać / udostępniać klasę konstruktora:
źródło
PersonBuilder
nie ma getterów, a jedynym sposobem na sprawdzenie bieżących wartości jest wywołanie,.Build()
aby zwrócić aPerson
. W ten sposób .Build może zweryfikować poprawnie skonstruowany obiekt, prawda? Czy to mechanizm, który ma zapobiegać użyciu obiektu „w budowie”?Build
operację, aby zapobiec złym i / lub niedokonanym obiektom.getAge
konstruktora może powrócić,null
jeśli ta właściwość nie została jeszcze określona. W przeciwieństwie do tegoPerson
klasa może wymusić niezmienność, że wiek nigdy nie może byćnull
, co jest niemożliwe, gdy budowniczy i rzeczywisty obiekt są pomieszane, jak wnew Person() .withName("John")
przykładzie OP , który na szczęście tworzyPerson
wiek bez wieku. Dotyczy to nawet klas zmiennych, ponieważ setery mogą wymuszać niezmienniki, ale nie mogą wymuszać wartości początkowych.Jednym z powodów byłoby zapewnienie, że wszystkie przekazywane dane są zgodne z regułami biznesowymi.
Twój przykład nie bierze tego pod uwagę, ale powiedzmy, że ktoś przekazał pusty ciąg znaków lub ciąg znaków specjalnych. Chcielibyście zrobić logikę opartą na upewnieniu się, że ich nazwa jest w rzeczywistości prawidłową nazwą (co w rzeczywistości jest bardzo trudnym zadaniem).
Możesz umieścić to wszystko w klasie Person, szczególnie jeśli logika jest bardzo mała (na przykład upewniając się, że wiek nie jest ujemny), ale gdy logika rośnie, sensowne jest jej rozdzielenie.
źródło
john
Trochę inaczej pod tym kątem niż w innych odpowiedziach.
withFoo
Podejście tutaj jest problematyczne, ponieważ zachowują się jak ustawiaczy ale są określone w taki sposób, aby uczynić go pojawiają niezmienność wsporniki klasy. W klasach Java, jeśli metoda modyfikuje właściwość, zwykle rozpoczyna się od „set”. Nigdy nie podobało mi się to jako standard, ale jeśli zrobisz coś innego, zaskoczy ludzi i to nie jest dobre. Istnieje inny sposób, aby wesprzeć niezmienność za pomocą podstawowego interfejsu API, który masz tutaj. Na przykład:Nie zapewnia wiele w zapobieganiu niewłaściwie wykonanym częściowo częściowo skonstruowanym obiektom, ale zapobiega zmianom istniejących obiektów. Prawdopodobnie jest to głupie dla tego rodzaju rzeczy (podobnie jak JB Builder). Tak, stworzysz więcej obiektów, ale nie jest to tak drogie, jak mogłoby się wydawać.
Tego rodzaju podejście będzie najczęściej stosowane w przypadku współbieżnych struktur danych, takich jak CopyOnWriteArrayList . A to wskazuje, dlaczego niezmienność jest ważna. Jeśli chcesz, aby Twój kod był wątkowo bezpieczny, prawie zawsze należy wziąć pod uwagę niezmienność. W Javie każdy wątek może przechowywać lokalną pamięć podręczną stanu zmiennej. Aby jeden wątek widział zmiany dokonane w innych wątkach, należy zastosować synchronizowany blok lub inną funkcję współbieżności. Każdy z nich doda trochę narzutu do kodu. Ale jeśli twoje zmienne są ostateczne, nie ma nic do zrobienia. Wartość będzie zawsze taka, jak na początku, dlatego wszystkie wątki widzą to samo bez względu na wszystko.
źródło
Innym powodem, który nie został tutaj wyraźnie wymieniony, jest to, że
build()
metoda może sprawdzić, czy wszystkie pola są „polami zawierającymi prawidłowe wartości (albo ustawione bezpośrednio, albo wyprowadzone z innych wartości innych pól), co jest prawdopodobnie najbardziej prawdopodobnym trybem awarii. inaczej by się to zdarzyło.Kolejną korzyścią jest to, że Twój
Person
obiekt będzie miał prostszy okres życia i prosty zestaw niezmienników. Wiesz, że kiedy maszPerson p
, maszp.name
i ważnep.age
. Żadna z twoich metod nie musi być zaprojektowana do radzenia sobie z sytuacjami takimi jak „cóż, jeśli wiek jest ustawiony, ale nie imię, lub co jeśli imię jest ustawione, ale nie wiek?” Zmniejsza to ogólną złożoność klasy.źródło
URI
, aFile
lub aFileInputStream
i używa wszystkiego, co zostało dostarczone, aby uzyskać a,FileInputStream
który ostatecznie przechodzi do wywołania konstruktora jako argMożna również zdefiniować konstruktora, aby zwracał interfejs lub klasę abstrakcyjną. Możesz użyć konstruktora do zdefiniowania obiektu, a konstruktor może na przykład określić, którą konkretną podklasę zwrócić, na podstawie ustawionych właściwości lub ustawionych dla nich właściwości.
źródło
Wzorzec konstruktora służy do budowania / tworzenia obiektu krok po kroku przez ustawienie właściwości, a po ustawieniu wszystkich wymaganych pól, należy zwrócić ostateczny obiekt za pomocą metody budowania Nowo utworzony obiekt jest niezmienny. Należy przede wszystkim zauważyć, że obiekt jest zwracany tylko wtedy, gdy wywoływana jest ostateczna metoda kompilacji. Zapewnia to, że wszystkie właściwości są ustawione na obiekt, a zatem obiekt nie jest w niespójnym stanie, gdy jest zwracany przez klasę konstruktora.
Jeśli nie użyjemy klasy konstruktora i bezpośrednio umieścimy wszystkie metody klasy konstruktora w samej klasie Person, musimy najpierw utworzyć obiekt, a następnie wywołać metody ustawiające na utworzonym obiekcie, co doprowadzi do niespójnego stanu obiektu między tworzeniem obiektu i ustawienie właściwości.
Tak więc, używając klasy konstruktora (tj. Jakiegoś zewnętrznego elementu innego niż sama klasa Person), zapewniamy, że obiekt nigdy nie będzie w niespójnym stanie.
źródło
Ponownie użyj obiektu konstruktora
Jak wspomnieli inni, niezmienność i weryfikacja logiki biznesowej wszystkich pól w celu sprawdzenia poprawności obiektu są głównymi przyczynami osobnego obiektu konstruktora.
Jednak ponowne użycie jest kolejną korzyścią. Jeśli chcę utworzyć instancję wielu bardzo podobnych obiektów, mogę wprowadzić niewielkie zmiany w obiekcie konstruktora i kontynuować tworzenie instancji. Nie ma potrzeby ponownego tworzenia obiektu konstruktora. To ponowne użycie pozwala konstruktorowi działać jako szablon do tworzenia wielu niezmiennych obiektów. Jest to niewielka korzyść, ale może być przydatna.
źródło
W rzeczywistości możesz mieć metody budujące w swojej klasie i nadal mieć niezmienność. Oznacza to tylko, że metody konstruktora zwrócą nowe obiekty, zamiast modyfikować istniejący.
Działa to tylko wtedy, gdy istnieje sposób na uzyskanie początkowego (ważnego / użytecznego) obiektu (np. Z konstruktora, który ustawia wszystkie wymagane pola lub metoda fabryczna, która ustawia wartości domyślne), a dodatkowe metody konstruktora zwracają następnie zmodyfikowane obiekty na podstawie na istniejącym. Te metody konstruktora muszą mieć pewność, że po drodze nie dostaniesz nieprawidłowych / niespójnych obiektów.
Oczywiście oznacza to, że będziesz mieć wiele nowych obiektów i nie powinieneś tego robić, jeśli tworzenie obiektów jest kosztowne.
Użyłem tego w kodzie testowym do tworzenia dopasowań Hamcrest dla jednego z moich obiektów biznesowych. Nie pamiętam dokładnego kodu, ale wyglądał mniej więcej tak (uproszczony):
Użyłbym go w ten sposób w testach jednostkowych (z odpowiednim importem statycznym):
źródło
name
ale nieage
), to koncepcja nie działa. Cóż, możesz zwracać coś takiego,PartiallyBuiltPerson
gdy nie jest to ważne, ale wydaje się, że to hack w celu zamaskowania Konstruktora.