Niedawno usunąłem moją odpowiedź Java w Code Review , która zaczęła się tak:
private Person(PersonBuilder builder) {
Zatrzymać. Czerwona flaga. Osoba budująca osobę zbuduje osobę; wie o Osobie. Klasa Person nie powinna wiedzieć nic o PersonBuilder - to tylko niezmienny typ. Utworzyłeś tutaj sprzęgło kołowe, w którym A zależy od B, który zależy od A.
Osoba powinna po prostu przyjąć swoje parametry; klient, który chce stworzyć Osobę bez budowania, powinien móc to zrobić.
Zostałem uderzony w głos głosowania i powiedziałem (cytując) czerwoną flagę, dlaczego? Implementacja ma ten sam kształt, co Joshua Bloch zademonstrował w swojej książce „Effective Java” (punkt # 2).
Wygląda więc na to, że jedynym słusznym sposobem implementacji wzorca konstruktora w Javie jest uczynienie konstruktora typem zagnieżdżonym (nie o to chodzi w tym pytaniu), a następnie uczynienie produktu (klasa budowanego obiektu ) weź zależność od konstruktora , na przykład:
private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; }
Ta sama strona Wikipedii dla tego samego wzorca Konstruktora ma zupełnie inną (i znacznie bardziej elastyczną) implementację dla C #:
//Represents a product created by the builder public class Car { public Car() { } public int Wheels { get; set; } public string Colour { get; set; } }
Jak widać, produkt tutaj nie wie nic o Builder
klasie, a wszystko, co go obchodzi, może zostać utworzony przez bezpośrednie wywołanie konstruktora, fabrykę abstrakcyjną ... lub konstruktora - o ile rozumiem, produkt o twórczym wzorze nigdy nie musi wiedzieć nic o tym, co go tworzy.
Dostałem kontrargument (który najwyraźniej jest wyraźnie broniony w książce Blocha), że wzorzec konstruktora mógłby zostać użyty do przerobienia typu, który miałby rozdęty konstruktor z dziesiątkami opcjonalnych argumentów. Zamiast więc trzymać się tego, co myślałem , że trochę przestudiowałem na tej stronie, i odkryłem, że jak podejrzewałem, ten argument nie dotyczy wody .
Więc o co chodzi? Po co wymyślać zbyt skomplikowane rozwiązania problemu, który w ogóle nie powinien istnieć? Jeśli na chwilę zdejmiemy Joshua Blocha z piedestału, czy możemy wymyślić jeden dobry, ważny powód, aby połączyć dwa konkretne typy i nazwać to najlepszą praktyką?
To wszystko cuchnie programowaniu kultu .
źródło
Odpowiedzi:
Nie zgadzam się z twoim twierdzeniem, że jest to czerwona flaga. Nie zgadzam się również z twoją reprezentacją kontrapunktu, że istnieje jeden właściwy sposób reprezentowania wzorca budowniczego.
Dla mnie jest jedno pytanie: Czy Konstruktor jest niezbędną częścią interfejsu API tego typu? Czy personBuilder jest niezbędną częścią interfejsu API osoby?
Jest całkowicie uzasadnione, że sprawdzona pod kątem ważności, niezmienna i / lub ściśle zamknięta klasa Person musi zostać utworzona za pośrednictwem dostarczonego przez nią Konstruktora (niezależnie od tego, czy ten Konstruktor jest zagnieżdżony czy sąsiadujący). W ten sposób Osoba może zachować wszystkie swoje pola prywatne lub pakiet-prywatne i końcowe, a także pozostawić klasę otwartą na modyfikację, jeśli jest w toku. (To może skończyć się zamknięte dla modyfikacji i otwarte na rozbudowę, ale to nie jest ważne teraz, i jest szczególnie kontrowersyjna dla niezmiennych obiektów danych).
Jeśli jest tak, że Osoba jest koniecznie tworzona za pomocą dostarczonego PersonBuilder jako część pojedynczego projektu interfejsu API na poziomie pakietu, wówczas okrągłe połączenie jest w porządku i nie jest tu wymagana czerwona flaga. W szczególności podałeś to jako niezaprzeczalny fakt, a nie opinię lub wybór projektu API, co mogło przyczynić się do złej odpowiedzi. Nie oddałbym ci głosu, ale nie obwiniam za to kogoś innego, a resztę dyskusji na temat „tego, co uzasadnia zdanie negatywne”, pozostawiam w Centrum pomocy Code Review i meta . Zdarzenia negatywne się zdarzają; to nie jest „policzek”.
Oczywiście, jeśli chcesz pozostawić wiele możliwości do zbudowania obiektu Person, wówczas PersonBuilder może stać się konsumentem lub narzędziemklasy Person, od której osoba nie powinna bezpośrednio zależeć. Ten wybór wydaje się bardziej elastyczny - kto nie chciałby więcej opcji tworzenia obiektów? - ale aby zachować gwarancje (takie jak niezmienność lub szeregowalność) nagle Osoba musi ujawnić inny rodzaj publicznej techniki tworzenia, na przykład bardzo długiego konstruktora . Jeśli Konstruktor ma reprezentować lub zamieniać konstruktor na wiele opcjonalnych pól lub konstruktor otwarty na modyfikacje, to długi konstruktor klasy może być szczegółem implementacji, który lepiej pozostawić ukryty. (Należy również pamiętać, że oficjalny i niezbędny Konstruktor nie wyklucza pisania własnego narzędzia budowlanego, tylko to, że narzędzie konstrukcyjne może korzystać z Konstruktora jako API, jak w moim pierwotnym pytaniu powyżej).
ps: Zauważam, że podana przez ciebie próbka recenzji kodu ma niezmienną Osobę, ale kontrprzykład z Wikipedii wymienia zmienny Samochód z pobieraczami i ustawiaczami. Łatwiej będzie zobaczyć pominięcie niezbędnych samochodów w samochodzie, jeśli zachowasz spójność języka i niezmienników.
źródło
String(StringBuilder)
konstruktora, który wydaje się być zgodny z tą „zasadą”, w której typ jest zależny od swojego konstruktora. Byłem oszołomiony, gdy zobaczyłem przeciążenie tego konstruktora. To nie jest tak, żeString
ma 42 parametry konstruktora ...toString()
jest uniwersalny.Rozumiem, jak denerwujące może być, gdy w debacie używane jest „odwołanie się do władzy”. Argumenty powinny opierać się na własnym IMO i chociaż nie jest błędem wskazywanie rad tak szanowanej osoby, to tak naprawdę nie można uznać za pełny argument sam w sobie, albowiem wiemy, że Słońce podróżuje po Ziemi, ponieważ Arystoteles tak powiedział .
Powiedziawszy to, myślę, że przesłanka twojego argumentu jest taka sama. Nigdy nie powinniśmy łączyć dwóch konkretnych rodzajów i nazywać to najlepszą praktyką, ponieważ ... Ktoś tak powiedział?
Aby argument, że sprzężenie jest problematyczne, był poprawny, muszą istnieć określone problemy, które stwarza. Jeśli takie podejście nie podlega tym problemom, nie można logicznie argumentować, że ta forma łączenia jest problematyczna. Więc jeśli chcesz tutaj powiedzieć, myślę, że musisz pokazać, w jaki sposób takie podejście podlega tym problemom i / lub w jaki sposób oddzielenie konstruktora poprawi projekt.
Od razu staram się zobaczyć, w jaki sposób połączone podejście spowoduje problemy. Klasy są całkowicie sprzężone, na pewno, ale konstruktor istnieje tylko w celu tworzenia instancji klasy. Innym sposobem spojrzenia na to byłoby rozszerzenie konstruktora.
źródło
String
dzieje się z przeciążeniem konstruktora przyjmującymStringBuilder
parametr (chociaż można dyskutować, czy aStringBuilder
jest konstruktorem , ale to inna historia).VBE
próbę, wraz z oknami, aktywnym okienkiem kodu, projektem i komponentem z modułem, który zawiera podany ciąg. Bez tego nie wiem, jak mógłbym napisać 80% testów w Rubberduck , przynajmniej bez dźgania mnie okiem za każdym razem, gdy na nie patrzę.Aby odpowiedzieć na pytanie, dlaczego typ byłby łączony z konstruktorem, konieczne jest zrozumienie, dlaczego ktokolwiek miałby używać konstruktora w pierwszej kolejności. W szczególności: Bloch zaleca używanie konstruktora, gdy masz dużą liczbę parametrów konstruktora. To nie jest jedyny powód, aby używać konstruktora, ale spekuluję, że jest to najczęściej. W skrócie, konstruktor zastępuje te parametry konstruktora i często konstruktor jest deklarowany w tej samej klasie, którą buduje. Zatem klasa zamykająca ma już wiedzę o konstruktorze i przekazanie jej jako argumentu konstruktora tego nie zmienia. Właśnie dlatego typ byłby sprzężony z jego konstruktorem.
Dlaczego posiadanie dużej liczby parametrów oznacza, że powinieneś używać konstruktora? Cóż, posiadanie dużej liczby parametrów nie jest niczym niezwykłym w prawdziwym świecie, ani nie narusza zasady pojedynczej odpowiedzialności. Jest do bani, gdy masz dziesięć parametrów String i próbujesz zapamiętać, które z nich są, i czy jest to piąty czy szósty String, który powinien być zerowy. Konstruktor ułatwia zarządzanie parametrami, a także może robić inne fajne rzeczy, o których powinieneś przeczytać książkę, aby dowiedzieć się więcej.
Kiedy używasz konstruktora, który nie jest sprzężony z jego typem? Zasadniczo, gdy komponenty obiektu nie są natychmiast dostępne, a obiekty, które mają te elementy, nie muszą wiedzieć o typie, który próbujesz zbudować, lub dodajesz konstruktor dla klasy, nad którą nie masz kontroli .
źródło
Person
Klasa nie wiem, co się jej tworzenia. Ma tylko konstruktor z parametrem, więc co? Nazwa typu parametru kończy się na ... Builder, który tak naprawdę niczego nie wymusza. W końcu to tylko nazwa.Konstruktor jest uwielbionym obiektem konfiguracji 1 . A obiekt konfiguracyjny to tak naprawdę grupowanie właściwości, które do siebie należą. Jeśli należą do siebie tylko w celu przekazania konstruktorowi klasy, niech tak będzie! Zarówno
origin
idestination
naStreetMap
przykład są typuPoint
. Czy możliwe byłoby przekazanie poszczególnych współrzędnych każdego punktu do mapy? Jasne, ale właściwościPoint
należą do siebie, a ponadto możesz mieć na nich wszelkiego rodzaju metody, które pomagają w ich budowie:1 Różnica polega na tym, że konstruktor nie jest zwykłym obiektem danych, ale umożliwia te powiązane wywołania settera. Chodzi
Builder
głównie o to, jak się budować.Dodajmy teraz trochę wzorca poleceń do miksu, ponieważ jeśli przyjrzysz się uważnie, budowniczy jest tak naprawdę poleceniem:
Specjalną częścią dla konstruktora jest to, że:
To, co jest przekazywane
Person
konstruktorowi, może je zbudować, ale niekoniecznie. Może to być zwykły stary obiekt konfiguracyjny lub konstruktor.źródło
Nie, są całkowicie w błędzie i masz absolutną rację. Ten model konstruktora jest po prostu głupi. To nie jest sprawa osoby, z której pochodzą argumenty konstruktora. Konstruktor to tylko wygoda dla użytkowników i nic więcej.
źródło
PersonBuilder
faktycznie jest istotną częściąPerson
API. W obecnej formie ta odpowiedź nie uzasadnia tego przypadku.