Вы должны либо использовать интерфейс (не наследование), либо какую-то механику свойств (возможно, даже что-то, что работает как структура описания ресурса).
Так что либо
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
или
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 {
}
Пояснение (правка):
Просто добавление необязательных полей в классе сущности
Особенно с помощью Optional-wrapper (правка: см. Ответ 4castle ). Я думаю, что это (добавление полей в исходную сущность) - это жизнеспособный способ добавления новых свойств в небольшом масштабе. Самая большая проблема этого подхода заключается в том, что он может работать против слабой связи.
Представьте, что ваш класс книги определен в отдельном проекте для вашей модели предметной области. Теперь вы добавляете другой проект, который использует модель домена для выполнения специальной задачи. Эта задача требует дополнительного свойства в классе книги. Либо вы получаете наследование (см. Ниже), либо вам нужно изменить модель общего домена, чтобы сделать новую задачу возможной. В последнем случае вы можете получить кучу проектов, которые все зависят от своих собственных свойств, добавленных в класс книги, в то время как сам класс книги в некотором роде зависит от этих проектов, потому что вы не можете понять класс книги без дополнительные проекты.
Почему наследование проблематично, когда речь идет о способе предоставления дополнительных свойств?
Когда я вижу ваш пример класса «Книга», я думаю о доменном объекте, который часто заканчивается множеством необязательных полей и подтипов. Представьте, что вы хотите добавить свойство для книг, которые включают в себя компакт-диск. В настоящее время существует четыре вида книг: книги, цветные книги, книги с компакт-дисками, цветные книги и компакт-диски. Вы не можете описать эту ситуацию с наследованием в Java.
С интерфейсами вы обходите эту проблему. Вы можете легко составить свойства определенного класса книги с помощью интерфейсов. Делегирование и состав позволят легко получить именно тот класс, который вам нужен. С наследованием вы обычно получаете некоторые необязательные свойства в классе, которые существуют только потому, что они нужны классу-брату.
Далее читаем, почему наследование часто является проблематичной идеей:
Почему сторонники ООП обычно считают наследование плохим
JavaWorld: почему расширяется зло
Проблема с реализацией набора интерфейсов для составления набора свойств
Когда вы используете интерфейсы для расширения, все в порядке, если у вас есть только небольшой набор из них. Особенно, когда ваша объектная модель используется и расширяется другими разработчиками, например, в вашей компании, количество интерфейсов будет расти. И в конечном итоге вы в конечном итоге создаете новый официальный «интерфейс свойств», который добавляет метод, который ваши коллеги уже использовали в своем проекте для клиента X для совершенно не связанного варианта использования - тьфу.
редактировать: еще один важный аспект упоминается gnasher729 . Вы часто хотите динамически добавлять дополнительные свойства к существующему объекту. С наследованием или интерфейсами вам придется воссоздавать весь объект с другим классом, который далеко не обязателен.
Когда вы ожидаете расширения вашей объектной модели до такой степени, вам будет лучше, если вы явно смоделируете возможность динамического расширения. Я предлагаю что-то вроде выше, где каждое «расширение» (в данном случае свойство) имеет свой собственный класс. Класс работает как пространство имен и идентификатор для расширения. Когда вы устанавливаете соглашения об именах пакетов соответствующим образом, этот способ позволяет бесконечное количество расширений, не загрязняя пространство имен для методов в исходном классе сущности.
При разработке игр вы часто сталкиваетесь с ситуациями, когда вы хотите составить поведение и данные во многих вариациях. Вот почему архитектурный шаблон entity-component-system стал довольно популярным в кругах разработчиков игр. Это также интересный подход, на который вы, возможно, захотите взглянуть, когда ожидаете много расширений для вашей объектной модели.