Czy w przypadku klas z polami opcjonalnymi lepiej jest użyć dziedziczenia lub właściwości zerowalnej? Rozważ ten przykład:
class Book {
private String name;
}
class BookWithColor extends Book {
private String color;
}
lub
class Book {
private String name;
private String color;
//when this is null then it is "Book" otherwise "BookWithColor"
}
lub
class Book {
private String name;
private Optional<String> color;
//when isPresent() is false it is "Book" otherwise "BookWithColor"
}
Kod zależny od tych 3 opcji to:
if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }
lub
if (book.getColor() != null) { book.getColor(); }
lub
if (book.getColor().isPresent()) { book.getColor(); }
Pierwsze podejście wydaje mi się bardziej naturalne, ale może jest mniej czytelne z powodu konieczności castingu. Czy jest jakiś inny sposób na osiągnięcie tego zachowania?
java
inheritance
class
null
Bojan VukasovicTest
źródło
źródło
Odpowiedzi:
To zależy od okoliczności. Konkretny przykład jest nierealny, ponieważ nie miałbyś podklasy wywoływanej
BookWithColor
w prawdziwym programie.Ogólnie jednak właściwość, która ma sens tylko dla niektórych podklas, powinna istnieć tylko w tych podklasach.
Na przykład jeśli
Book
maPhysicalBook
iDigialBook
jak potomkowie, toPhysicalBook
może miećweight
własności, aDigitalBook
nasizeInKb
własność. AleDigitalBook
nie będzieweight
i na odwrót.Book
nie będzie miał żadnej właściwości, ponieważ klasa powinna mieć właściwości wspólne dla wszystkich potomków.Lepszym przykładem jest przyglądanie się klasom z prawdziwego oprogramowania.
JSlider
Składnik ma polemajorTickSpacing
. Ponieważ tylko suwak ma „tyknięcia”, to pole ma sens tylko dlaJSlider
jego potomków. Byłoby bardzo mylące, gdyby inne elementy rodzeństwa, takie jak pole,JButton
miałymajorTickSpacing
pole.źródło
Ważna kwestia, o której najwyraźniej nie wspomniano: W większości języków instancje klasy nie mogą zmienić klasy, której są instancjami. Jeśli więc masz książkę bez koloru i chcesz dodać kolor, musisz utworzyć nowy obiekt, jeśli używasz różnych klas. I wtedy prawdopodobnie będziesz musiał zastąpić wszystkie odniesienia do starego obiektu odniesieniami do nowego obiektu.
Jeśli „książki bez koloru” i „książki z kolorem” są instancjami tej samej klasy, wówczas dodanie koloru lub usunięcie koloru będzie znacznie mniejszym problemem. (Jeśli interfejs użytkownika wyświetla listę „książek z kolorem” i „książek bez koloru”, interfejs ten musiałby się oczywiście zmienić, ale przypuszczam, że i tak należy się tym zająć, podobnie jak „lista czerwonych” książki ”i„ lista zielonych książek ”).
źródło
Pomyśl o JavaBean (jak twój
Book
) jako rekordzie w bazie danych. Opcjonalne kolumny są,null
gdy nie mają żadnej wartości, ale jest to całkowicie legalne. Dlatego twoja druga opcja:Jest najbardziej rozsądny. 1
Uważaj jednak na sposób korzystania z
Optional
klasy. Na przykład tak nie jestSerializable
, co zwykle jest cechą JavaBean. Oto kilka wskazówek od Stephena Colebourne'a :W związku z tym, w ciągu swojej klasie należy użyć
null
do reprezentowania, że pole nie jest obecny, ale kiedycolor
odchodzi sięBook
(jako typ zwracany) powinny być opakowane ze związkiemOptional
.Zapewnia to przejrzysty wygląd i czytelniejszy kod.
1 Jeśli masz zamiar dotyczący
BookWithColor
aby upakować całą „klasa” książek, które wyspecjalizowane funkcje w stosunku do innych książek, wtedy byłoby sensu używać dziedziczenia.źródło
Optional
klasę do Javy właśnie w tym celu. To może nie być OO, ale z pewnością jest to ważna opinia.Powinieneś albo użyć interfejsu (a nie dziedziczenia), albo jakiegoś mechanika własności (może nawet czegoś, co działa jak struktura opisu zasobów).
Więc albo
lub
Wyjaśnienie (edycja):
Po prostu dodając opcjonalne pola w klasie encji
Zwłaszcza w przypadku Opcjonalnego opakowania(edycja: patrz odpowiedź 4castle ) Myślę, że to (dodanie pól w oryginalnej encji) jest realnym sposobem na dodanie nowych właściwości na małą skalę. Największym problemem związanym z tym podejściem jest to, że może on działać przeciwko niskiemu sprzężeniu.Wyobraź sobie, że twoja klasa książek jest zdefiniowana w dedykowanym projekcie dla twojego modelu domeny. Teraz dodajesz kolejny projekt, który używa modelu domeny do wykonania specjalnego zadania. To zadanie wymaga dodatkowej właściwości w klasie książek. Albo skończysz z dziedziczeniem (patrz poniżej), albo musisz zmienić wspólny model domeny, aby umożliwić nowe zadanie. W tym drugim przypadku możesz skończyć z wieloma projektami, które wszystkie zależą od ich własnych właściwości dodanych do klasy book, podczas gdy sama klasa book w pewien sposób zależy od tych projektów, ponieważ nie jesteś w stanie zrozumieć klasy book bez dodatkowe projekty.
Dlaczego dziedziczenie jest problematyczne, jeśli chodzi o sposób zapewnienia dodatkowych właściwości?
Kiedy widzę przykładową klasę „Book”, myślę o obiekcie domeny, który często ma wiele opcjonalnych pól i podtypów. Wyobraź sobie, że chcesz dodać właściwość do książek zawierających dysk CD. Istnieją teraz cztery rodzaje książek: książki, książki z kolorami, książki z CD, książki z kolorami i CD. Nie można opisać tej sytuacji za pomocą dziedziczenia w Javie.
Dzięki interfejsom można obejść ten problem. Możesz łatwo skomponować właściwości określonej klasy książki za pomocą interfejsów. Delegacja i skład ułatwią ci zdobycie dokładnie takiej klasy, jakiej chcesz. W przypadku dziedziczenia zazwyczaj uzyskuje się pewne opcjonalne właściwości w klasie, które są dostępne tylko dlatego, że potrzebuje ich klasa rodzeństwa.
Czytaj dalej, dlaczego dziedziczenie jest często problematycznym pomysłem:
Dlaczego dziedziczenie jest ogólnie postrzegane przez zwolenników OOP jako coś złego
JavaWorld: Dlaczego rozszerzenie jest złem
Problem z implementacją zestawu interfejsów w celu skomponowania zestawu właściwości
Gdy używasz interfejsów do rozszerzenia, wszystko jest w porządku, o ile masz ich tylko niewielki zestaw. Zwłaszcza, gdy Twój model obiektowy jest używany i rozszerzany przez innych programistów, np. W Twojej firmie, liczba interfejsów wzrośnie. I w końcu tworzysz nowy oficjalny „interfejs własności”, który dodaje metodę, którą koledzy już zastosowali w swoim projekcie dla klienta X dla zupełnie niezwiązanego przypadku użycia - Ugh.
edycja: Kolejny ważny aspekt wspomniany jest przez gnasher729 . Często chcesz dynamicznie dodawać opcjonalne właściwości do istniejącego obiektu. W przypadku dziedziczenia lub interfejsów musiałbyś odtworzyć cały obiekt z inną klasą, co jest dalekie od opcjonalnego.
Kiedy spodziewasz się rozszerzeń swojego modelu obiektowego w takim stopniu, lepiej skorzystasz z jawnego modelowania możliwości dynamicznego rozszerzenia. Proponuję coś takiego jak powyżej, w którym każde „rozszerzenie” (w tym przypadku właściwość) ma swoją własną klasę. Klasa działa jako przestrzeń nazw i identyfikator rozszerzenia. Gdy odpowiednio ustawisz konwencje nazewnictwa pakietów, w ten sposób zezwolisz na nieskończoną liczbę rozszerzeń bez zanieczyszczania przestrzeni nazw metodami w oryginalnej klasie encji.
Podczas tworzenia gry często napotykasz sytuacje, w których chcesz komponować zachowania i dane w wielu odmianach. Właśnie dlatego wzorzec architektury jednostka-komponent-system stał się bardzo popularny w kręgach twórców gier. Jest to również interesujące podejście, na które warto spojrzeć, gdy oczekujesz wielu rozszerzeń swojego modelu obiektowego.
źródło
PropertiesHolder
prawdopodobnie byłaby to klasa abstrakcyjna. Ale co sugerowałbyś jako implementację dlagetProperty
lubgetPropertyValue
? Dane nadal muszą być gdzieś przechowywane w jakimś polu instancji. A może użyłbyś aCollection<Property>
do przechowywania właściwości zamiast pól?Book
przedłużaniePropertiesHolder
ze względu na przejrzystość.PropertiesHolder
Zwykle powinien być zarejestrowany we wszystkich klasach, które potrzebują tej funkcji. Implementacja będzie zawieraćMap<Class<? extends Property<?>>,Property<?>>
coś w tym rodzaju. Jest to brzydka część implementacji, ale zapewnia naprawdę fajną platformę, która działa nawet z JAXB i JPA.Jeśli
Book
klasa jest pochodna bez właściwości koloru, jak poniżejto dziedziczenie jest uzasadnione.
źródło
color
jest prywatny, to żadna klasa pochodna i tak nie powinna się tym przejmowaćcolor
. Po prostu dodaj kilka konstruktorów doBook
tego ignorowania,color
a domyślnie będzie tonull
.