Mam dwa przedmioty reprezentujące „Bar / Klub” (miejsce, w którym pijesz / spędzasz czas z przyjaciółmi).
W jednym scenariuszu potrzebuję nazwy paska, adresu, odległości, hasła
W innym scenariuszu potrzebuję nazwy paska, adresu, adresu URL strony internetowej, logo
Mam więc dwa obiekty reprezentujące to samo, ale z różnymi polami.
Lubię używać niezmiennych obiektów, więc wszystkie pola są ustawiane z konstruktora .
Jedną z opcji jest posiadanie dwóch konstruktorów i zerowanie pozostałych pól, tj .:
class Bar {
private final String name;
private final Distance distance;
private final Url url;
public Bar(String name, Distance distance){
this.name = name;
this.distance = distance;
this.url = null;
}
public Bar(String name, Url url){
this.name = name;
this.distance = null;
this.url = url;
}
// getters
}
Nie podoba mi się to, ponieważ musiałbyś zerować sprawdzanie, gdy używasz getterów
W moim prawdziwym przykładzie pierwszy scenariusz ma 3 pola, a drugi scenariusz ma około 10, więc prawdziwym bólem byłoby posiadanie dwóch konstruktorów , ilość pól, które musiałbym zadeklarować jako zerowe, a kiedy obiekt byłby w użyciu, nie byłoby ' t wiem Bar
, gdzie używasz, a więc jakie pola byłyby puste, a co nie.
Jakie inne opcje mam?
Dwie klasy nazwane BarPreview
i Bar
?
Jakiś typ dziedziczenia / interfejsu?
Coś jeszcze, co jest niesamowite?
Bar
jako identyfikatora!You should only ask practical, answerable questions based on actual problems that you face.
i dokładnie to się tutaj dziejeOdpowiedzi:
Moje myśli:
„Pasek”, reprezentowany w domenie, zawiera wszystkie rzeczy, które mogą być potrzebne w obu miejscach: imię i nazwisko, adres, adres URL, logo, hasło i „odległość” (domyślam się z lokalizacji wnioskodawcy). Dlatego w Twojej domenie powinna istnieć jedna klasa „Bar”, która jest wiarygodnym źródłem danych dla jednego paska, bez względu na to, gdzie dane zostaną wykorzystane później. Ta klasa powinna być modyfikowalna, aby zmiany danych paska mogły być wprowadzane i zapisywane w razie potrzeby.
Istnieją jednak dwa miejsca, w których potrzebne są dane tego obiektu Bar, i oba wymagają tylko podzbioru (i nie chcesz, aby te dane były zmieniane). Zwykle odpowiedzią jest „obiekt przesyłania danych” lub DTO; POJO (zwykły obiekt Java) zawierający niezmienne gettery właściwości. Te DTO można wytworzyć przez wywołanie metody na głównym obiekcie domeny Bar: „toScenario1DTO ()” i „toScenario2DTO ()”; wyniki są uwodnionym DTO (co oznacza, że wystarczy użyć długiego, skomplikowanego konstruktora w jednym miejscu).
Jeśli kiedykolwiek musiałeś odesłać dane z powrotem do głównej klasy domeny (aby je zaktualizować; jaki jest sens danych, jeśli nie możesz ich zmienić w razie potrzeby w celu odzwierciedlenia obecnego stanu rzeczywistego świata?), Możesz zbudować jeden z DTO lub użyj nowego, zmiennego DTO i wróć do klasy Bar, używając metody „updateFromDto ()”.
EDYCJA: aby podać przykład:
Klasy Adres, Odległość i Url powinny być same w sobie niezmienne lub, jeśli są używane w DTO, należy je zastąpić niezmiennymi odpowiednikami.
źródło
Jeśli zależy Ci tylko na podzbiorze właściwości i chcesz się upewnić, że się nie pomieszają, utwórz dwa interfejsy i użyj go do rozmowy z obiektem podstawowym.
źródło
Bar
klasyBudowniczy (lub coś podobnego do niego) mogą być wykorzystane tutaj.
Posiadanie niezmiennych obiektów jest godną podziwu rzeczą, ale w rzeczywistości z Reflection in Java nic nie jest naprawdę bezpieczne ;-).
źródło
HawaiianPizzaBuilder
działa, ponieważ wartości, których potrzebuje, są zakodowane na stałe. Jak jednak użyć tego wzorca, jeśli wartości zostaną pobrane i przekazane do konstruktora?HawaiianPizzaBuilder
Nadal mają wszystkie pobierające żeSpicyPizzaBuilder
ma tak null jest możliwe. Chyba że połączysz to z @JarrodsNull Object Pattern
. Przykład kodu zBar
poprowadziłby twój punktKluczową kwestią jest tutaj różnica między tym, czym jest „słupek”, a tym, jak go używasz w takim czy innym kontekście.
Pasek jest pojedynczym bytem w świecie rzeczywistym (lub sztucznym świecie, takim jak gra) i powinna go reprezentować tylko JEDNA instancja obiektu. Kiedykolwiek później nie utworzysz tego wystąpienia z segmentu kodu, ale załadujesz go z pliku konfiguracyjnego lub bazy danych, będzie to bardziej widoczne.
(Aby być jeszcze bardziej ezoterycznym: każda instancja Bar ma inny cykl życia niż obiekt, który ją reprezentuje podczas działania programu. Nawet jeśli masz kod źródłowy, który tworzy tę instancję, oznacza to, że encja Bar zgodnie z opisem „istnieje” ”w stanie uśpienia w kodzie źródłowym i„ przebudź się ”, gdy ten kod faktycznie utworzy go w pamięci ...)
Przepraszam za długi początek, ale mam nadzieję, że to wyjaśnia mój punkt widzenia. Masz JEDNĄ klasę Bar posiadającą wszystkie atrybuty, których kiedykolwiek potrzebujesz, i jedną instancję Bar reprezentującą każdą jednostkę Bar. Jest to poprawne w kodzie i niezależne od tego, jak chcesz zobaczyć to samo wystąpienie w różnych kontekstach .
Te ostatnie mogą być reprezentowane przez dwa różne interfejsy , które zawierają wymagane metody dostępu (getName (), getURL (), getDistance ()), a klasa Bar powinna zaimplementować oba. (I być może „odległość” zmieni się na „lokalizacja”, a getDistance () stanie się obliczeniem z innej lokalizacji :-))
Ale stworzenie jest dla encji Bar, a nie dla sposobu, w jaki chcesz jej używać: jeden konstruktor, wszystkie pola.
ZMIENIONO: Mogę pisać kod! :-)
źródło
Odpowiedni wzór
To, czego szukasz, jest najczęściej określane jako
Null Object Pattern
. Jeśli nie podoba ci się nazwa, możesz nazwać ją tąUndefined Value Pattern
samą semantyką inną etykietą. Czasami ten wzorzec jest nazywanyPoison Pill Pattern
.We wszystkich tych przypadkach Obiekt jest zamiennikiem lub zastępuje go wartością zerową
Default Value
zamiast wartościnull. It doesn't replace the semantic of
zerowej,but makes it easier to work with the data model in a more predictable way because
która nigdy nie powinna być poprawnym stanem.Jest to wzorzec, w którym rezerwujesz specjalne wystąpienie danej klasy w celu reprezentowania innej
null
opcji jakoDefault Value
. W ten sposób nie musisz sprawdzać w stosunku donull
, możesz sprawdzić tożsamość w stosunku do znanegoNullObject
wystąpienia. Możesz bezpiecznie wywoływać metody na nim i tym podobne, nie martwiąc się o toNullPointerExceptions
.W ten sposób zastępujesz swoje
null
zadania ich reprezentatywnymiNullObject
instancjami i gotowe.Właściwa analiza obiektowa
W ten sposób możesz mieć wspólne
Interface
dla polimorfizmu i nadal mieć ochronę przed martwieniem się o brak danych w konkretnych implementacjach interfejsu. Dlatego niektóreBar
mogą nie mieć obecności w sieci, a niektóre mogą nie mieć danych o lokalizacji w czasie budowy.Null Object Patter
pozwala podać wartość domyślną dla każdego z nich, czylimarker
dla danych, które mówią to samo, nic tu nie podano, bez zajmowania się sprawdzaniemNullPointerException
całego miejsca.Właściwe projektowanie obiektowe
Najpierw
abstract
implementacja, która jest super zestawem wszystkich atrybutów, które jednocześnieBar
iClub
współużytkują.Następnie możesz zaimplementować podklasy tej
Establishment
klasy i dodać tylko określone rzeczy, których potrzebujesz dla każdej z klasBar
iClub
klas, które nie dotyczą drugiej.Trwałość
Te obiekty zastępcze, jeśli są poprawnie zbudowane, mogą być przezroczyście przechowywane w bazie danych bez specjalnej obsługi.
Dowód na przyszłość
Jeśli zdecydujesz się wskoczyć na modę Inversion of Control / Dependency Injection później, ten wzór ułatwia wstrzyknięcie również tych obiektów znaczników.
źródło
Myślę, że problem polega na tym, że nie modelujesz paska w żadnym z tych scenariuszy (i modelujesz dwa różne problemy, obiekty itp.). Jeśli zobaczę bar klasy, oczekiwałbym pewnej funkcjonalności związanej z napojami, menu, dostępnymi siedzeniami, a twój obiekt nie ma tego. Jeśli widzę zachowanie twoich obiektów, modelujesz informacje o zakładzie. Bar jest tym, czego używasz w tej chwili, ale nie jest to samo zachowanie, które wdrażają. (W innym kontekście: jeśli modelujesz małżeństwo, będziesz mieć dwie zmienne instancji Osoba żona; Osoba mąż; żona jest bieżącą rolą, jaką w tym momencie nadajesz temu obiektowi, ale obiekt jest nadal Osobą). Zrobiłbym coś takiego:
źródło
Naprawdę nie trzeba tego komplikować. Potrzebujesz dwóch różnych rodzajów obiektów? Zrób dwie klasy.
Jeśli chcesz na nich działać równo, rozważ dodanie interfejsu lub użycie refleksji.
źródło
null
. Zapamiętajnull
oznacza brak danych , a nie brak atrybutu.Moja odpowiedź dla każdego, kto ma tego typu problem, polega na podzieleniu go na możliwe do opanowania kroki .
BarOne
iBarTwo
(lub wywołaj je obie,Bar
ale w różnych pakietach)bar
zmieniają nazwę obrażającej klasy na to, co teraz reprezentujeinterface
lubsuperclass
ze wspólnym zachowanieminterface
lubsuperclass
możesz utworzyćbuilder
lub,factory
aby utworzyć / pobrać obiekty implementacji(4 i 5 to inne odpowiedzi na to pytanie)
źródło
Potrzebujesz klasy podstawowej, na przykład Lokalizacja, która ma nazwę i adres . Teraz masz dwie klasy Bar i BarPreview rozszerzają klasę bazową Lokalizacja . W każdej klasie inicjujesz zmienne wspólne superklasy, a następnie zmienne unikalne:
Podobnie jest w przypadku klasy BarPreview.
Jeśli to sprawia, że śpisz lepiej w nocy, zamień wszystkie wystąpienia lokalizacji w moim kodzie na AnythingYouThinkWouldBeAnAp odpowiedniNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.
źródło
final
.final
w polach @David.Bar
nie powinno toextend Location
jednak mieć sensu. Być możeBar extends BaseBar
iBarPreview extends BaseBar
te nazwy też nie brzmią zbyt dobrze, liczyłem na coś bardziej eleganckiego