Для классов с необязательными полями лучше использовать наследование или свойство, допускающее значение NULL? Рассмотрим этот пример:
class Book {
private String name;
}
class BookWithColor extends Book {
private String color;
}
или
class Book {
private String name;
private String color;
//when this is null then it is "Book" otherwise "BookWithColor"
}
или
class Book {
private String name;
private Optional<String> color;
//when isPresent() is false it is "Book" otherwise "BookWithColor"
}
Код в зависимости от этих 3 вариантов будет:
if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }
или
if (book.getColor() != null) { book.getColor(); }
или
if (book.getColor().isPresent()) { book.getColor(); }
Первый подход выглядит более естественным для меня, но, возможно, он менее читабелен из-за необходимости выполнять приведение. Есть ли другой способ добиться этого поведения?
java
inheritance
class
null
Боян ВукасовичТест
источник
источник
Ответы:
Это зависит от обстоятельств. Конкретный пример нереалистичен, так как у вас не будет подкласса, вызываемого
BookWithColor
в реальной программе.Но в целом свойство, которое имеет смысл только для определенных подклассов, должно существовать только в этих подклассах.
Например , если
Book
естьPhysicalBook
и вDigialBook
качестве потомков, тоPhysicalBook
может иметьweight
свойство, иDigitalBook
вsizeInKb
собственность. НоDigitalBook
не будетweight
и наоборот.Book
не будет иметь ни одного свойства, так как класс должен иметь свойства, общие для всех потомков.Лучший пример - смотреть на классы из реального программного обеспечения.
JSlider
Компонент имеет полеmajorTickSpacing
. Так как только у слайдера есть «галочки», это поле имеет смысл только дляJSlider
и его потомков. Было бы очень странно, если бы у других родственных компонентов, такихJButton
какmajorTickSpacing
поле, было поле.источник
Важный момент, который, кажется, не был упомянут: в большинстве языков экземпляры класса не могут изменить класс, к которому они относятся. Так что если у вас была книга без цвета, и вы хотели добавить цвет, вам нужно было бы создать новый объект, если вы используете разные классы. И тогда вам, вероятно, придется заменить все ссылки на старый объект ссылками на новый объект.
Если «книги без цвета» и «книги с цветом» являются экземплярами одного и того же класса, то добавление цвета или удаление цвета будет намного меньшей проблемой. (Если ваш пользовательский интерфейс отображает список «книг с цветом» и «книг без цвета», то этот пользовательский интерфейс, очевидно, должен был бы измениться, но я ожидаю, что с этим вам все равно придется справиться, как в «красном списке»). книги "и" список зеленых книг ").
источник
Думайте о JavaBean (как ваш
Book
) как о записи в базе данных. Необязательные столбцы,null
когда они не имеют значения, но это совершенно законно. Поэтому ваш второй вариант:Это самый разумный. 1
Будьте осторожны с тем, как вы используете
Optional
класс. Например, это не такSerializable
, что обычно является характеристикой JavaBean. Вот несколько советов от Стивена Колебурна :Таким образом, в своем классе , вы должны использовать ,
null
чтобы представить , что поле нет, но когдаcolor
листья наBook
(как тип возвращаемого значения ) он должен быть обернут сOptional
.Это обеспечивает понятный дизайн и более читаемый код.
1 Если вы собираетесь
BookWithColor
инкапсулировать целый «класс» книг, обладающих специализированными возможностями по сравнению с другими книгами, то имеет смысл использовать наследование.источник
Optional
класса в Java для этой конкретной цели. Это может быть не ОО, но это, безусловно, обоснованное мнение.Вы должны либо использовать интерфейс (не наследование), либо какую-то механику свойств (возможно, даже что-то, что работает как структура описания ресурса).
Так что либо
или
Пояснение (правка):
Просто добавление необязательных полей в классе сущности
Особенно с помощью Optional-wrapper(правка: см. Ответ 4castle ). Я думаю, что это (добавление полей в исходную сущность) - это жизнеспособный способ добавления новых свойств в небольшом масштабе. Самая большая проблема этого подхода заключается в том, что он может работать против слабой связи.Представьте, что ваш класс книги определен в отдельном проекте для вашей модели предметной области. Теперь вы добавляете другой проект, который использует модель домена для выполнения специальной задачи. Эта задача требует дополнительного свойства в классе книги. Либо вы получаете наследование (см. Ниже), либо вам нужно изменить модель общего домена, чтобы сделать новую задачу возможной. В последнем случае вы можете получить кучу проектов, которые все зависят от своих собственных свойств, добавленных в класс книги, в то время как сам класс книги в некотором роде зависит от этих проектов, потому что вы не можете понять класс книги без дополнительные проекты.
Почему наследование проблематично, когда речь идет о способе предоставления дополнительных свойств?
Когда я вижу ваш пример класса «Книга», я думаю о доменном объекте, который часто заканчивается множеством необязательных полей и подтипов. Представьте, что вы хотите добавить свойство для книг, которые включают в себя компакт-диск. В настоящее время существует четыре вида книг: книги, цветные книги, книги с компакт-дисками, цветные книги и компакт-диски. Вы не можете описать эту ситуацию с наследованием в Java.
С интерфейсами вы обходите эту проблему. Вы можете легко составить свойства определенного класса книги с помощью интерфейсов. Делегирование и состав позволят легко получить именно тот класс, который вам нужен. С наследованием вы обычно получаете некоторые необязательные свойства в классе, которые существуют только потому, что они нужны классу-брату.
Далее читаем, почему наследование часто является проблематичной идеей:
Почему сторонники ООП обычно считают наследование плохим
JavaWorld: почему расширяется зло
Проблема с реализацией набора интерфейсов для составления набора свойств
Когда вы используете интерфейсы для расширения, все в порядке, если у вас есть только небольшой набор из них. Особенно, когда ваша объектная модель используется и расширяется другими разработчиками, например, в вашей компании, количество интерфейсов будет расти. И в конечном итоге вы в конечном итоге создаете новый официальный «интерфейс свойств», который добавляет метод, который ваши коллеги уже использовали в своем проекте для клиента X для совершенно не связанного варианта использования - тьфу.
редактировать: еще один важный аспект упоминается gnasher729 . Вы часто хотите динамически добавлять дополнительные свойства к существующему объекту. С наследованием или интерфейсами вам придется воссоздавать весь объект с другим классом, который далеко не обязателен.
Когда вы ожидаете расширения вашей объектной модели до такой степени, вам будет лучше, если вы явно смоделируете возможность динамического расширения. Я предлагаю что-то вроде выше, где каждое «расширение» (в данном случае свойство) имеет свой собственный класс. Класс работает как пространство имен и идентификатор для расширения. Когда вы устанавливаете соглашения об именах пакетов соответствующим образом, этот способ позволяет бесконечное количество расширений, не загрязняя пространство имен для методов в исходном классе сущности.
При разработке игр вы часто сталкиваетесь с ситуациями, когда вы хотите составить поведение и данные во многих вариациях. Вот почему архитектурный шаблон entity-component-system стал довольно популярным в кругах разработчиков игр. Это также интересный подход, на который вы, возможно, захотите взглянуть, когда ожидаете много расширений для вашей объектной модели.
источник
PropertiesHolder
вероятно, будет абстрактный класс. Но что бы вы предложили для реализацииgetProperty
илиgetPropertyValue
? Данные все еще должны храниться в каком-то поле экземпляра где-нибудь. Или вы бы использовалиCollection<Property>
для хранения свойств вместо использования полей?Book
расширениеPropertiesHolder
для ясности.PropertiesHolder
Обычно должен быть членом во всех классах , которые нуждаются в этой функции. Реализация будет содержатьMap<Class<? extends Property<?>>,Property<?>>
или что-то вроде этого. Это уродливая часть реализации, но она обеспечивает действительно хорошую среду, которая работает даже с JAXB и JPA.Если
Book
класс выводим без свойства цвета, как показано нижетогда наследование разумно.
источник
color
это private, то любой производный класс вообще не должен интересоватьсяcolor
. Просто добавьте несколько конструкторов кBook
этому игнорированиюcolor
, и он будет по умолчаниюnull
.