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
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
lub
class PropertiesHolder {
<T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
<V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
String getName();
T getValue();
}
class ColorProperty implements Property<Color> {
public Color getValue() { ... }
public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}
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.