For classes with optional fields, is it better to use inheritance or a nullable property? Consider this example:
class Book {
private String name;
}
class BookWithColor extends Book {
private String color;
}
or
class Book {
private String name;
private String color;
//when this is null then it is "Book" otherwise "BookWithColor"
}
or
class Book {
private String name;
private Optional<String> color;
//when isPresent() is false it is "Book" otherwise "BookWithColor"
}
The code depending on these 3 options would be:
if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }
or
if (book.getColor() != null) { book.getColor(); }
or
if (book.getColor().isPresent()) { book.getColor(); }
The first approach looks more natural to me, but maybe it is less readable because of the necessity to do casting. Is there some other way to achieve this behavior?
-
1I am afraid the solution comes down to your definition of business rules. I mean, if all your books may have a color but the color is optional, then inheritance is overkill and actually unnecessary, because you want all your books to know the color property exists. On the other hand, if you can have both books who know nothing about color what so ever and books who have a color, creating specialized classes is not bad. Even then, I'd probably go for composition over inheritance.Andy– Andy2016年05月01日 20:35:33 +00:00Commented May 1, 2016 at 20:35
-
To make it a bit more specific - there are 2 types of books - one with color and one without. So not all books may have a color.Bojan VukasovicTest– Bojan VukasovicTest2016年05月01日 21:23:12 +00:00Commented May 1, 2016 at 21:23
-
1If there really is a case, where book does not have color at all, in your case inheritance is the simplest way how to extend the properties of a colorful book. In this case it does not look like you'd be breaking anything by inheriting the base book class and adding a color property to it. You can read about liskov substitution principle to learn more about cases when extending a class is allowed and when it is not.Andy– Andy2016年05月01日 21:55:43 +00:00Commented May 1, 2016 at 21:55
-
4Can you identify a behavior that you want from books with coloring? Can you find a common behavior among books with and without coloring, where the books with coloring should behave slightly differently? If so, this is a good case for OOP with different types, and, the idea would be to move the behaviors into the classes instead of interrogating property presence and value externally.Erik Eidt– Erik Eidt2016年05月01日 23:28:25 +00:00Commented May 1, 2016 at 23:28
-
1If the possible book colors are known ahead of time, how about an Enum field for BookColor with an option for BookColor.NoColor?GHP– GHP2016年05月02日 12:38:52 +00:00Commented May 2, 2016 at 12:38
5 Answers 5
It depends on the circumstances. The specific example is unrealistic since you wouldn't have a subclass called BookWithColor
in a real program.
But in general, a property which only makes sense for certain subclasses should only exist in those subclasses.
For example if Book
has PhysicalBook
and DigialBook
as descendents, then PhysicalBook
might have a weight
property, and DigitalBook
a sizeInKb
property. But DigitalBook
will not have weight
and vice versa. Book
will have neither property, since a class should only have properties shared by all descendents.
A better example is looking at classes from real software. The JSlider
component has the field majorTickSpacing
. Since only a slider has "ticks", this field only makes sense for JSlider
and its descendants. It would be very confusing if other sibling components like JButton
had a majorTickSpacing
field.
-
or you could bastract the weight to be able to reflect both, but that's probably too much.Walfrat– Walfrat2016年05月02日 07:14:06 +00:00Commented May 2, 2016 at 7:14
-
3@Walfrat: It would be a really bad idea to have the same field represent completely different things in different subclasses.JacquesB– JacquesB2016年05月02日 07:54:06 +00:00Commented May 2, 2016 at 7:54
-
What if a book has both physical and digital editions? You would have to create a different class for every permutation of having or not having each optional field. I think the best solution would just to allow some fields to be omitted or not depending on the book. You've cited a completely different situation where the two classes would behave different functionally. That's not what this question implies. They just need to model a data structure.castletheperson– castletheperson2016年05月02日 22:10:35 +00:00Commented May 2, 2016 at 22:10
-
@4castle: You would need seperate classes per edition then since each edition might also have different price. You can't solve this with optional fields. But we don't know from the example if the op want different behavior for books with color or "colorless books", so it is impossible to know what is the correct modelling in this case.JacquesB– JacquesB2016年05月03日 05:52:13 +00:00Commented May 3, 2016 at 5:52
-
3agree with @JacquesB . you can't give a universally correct solution for "modelling books", because it depends on what problem you are trying to solve and what your business is. I think it's pretty safe to say that defining class hierarchies where you violate liskov is categorically Wrong(tm) though (yeah yeah, your case is probably very special and warrants it (although I doubt it))sara– sara2016年05月03日 10:11:36 +00:00Commented May 3, 2016 at 10:11
An important point that doesn't seem to have been mentioned: In most languages, instances of a class cannot change which class they are instances of. So if you had a book without a colour, and you wanted to add a colour, you'd need to create a new object if you are using different classes. And then you'd probably need to replace all references to the old object with references to the new object.
If "books without colour" and "books with colour" are instances of the same class, then adding the colour or removing the colour will be much less of a problem. (If your user interface shows a list of "books with colour" and "books without colour" then that user interface would have to change obviously, but I'd expect that's something you need to handle anyway, similar to a "list of red books" and "list of green books").
-
This is really an important point! It defines whether one should consider a collection of optional properties instead of class attributes.chromanoid– chromanoid2016年05月04日 08:29:59 +00:00Commented May 4, 2016 at 8:29
Think of a JavaBean (like your Book
) as a record in a database. Optional columns are null
when they have no value, but it is perfectly legal. Therefore, your second option:
class Book {
private String name;
private String color; // null when not applicable
}
Is the most reasonable.1
Be careful though with how you use the Optional
class. For example, it isn't Serializable
, which is usually a characteristic of a JavaBean. Here are some tips from Stephen Colebourne:
- Do not declare any instance variable of type
Optional
.- Use
null
to indicate optional data within the private scope of a class.- Use
Optional
for getters that access the optional field.- Do not use
Optional
in setters or constructors.- Use
Optional
as a return type for any other business logic methods that have an optional result.
Therefore, within your class you should use null
to represent that the field is not present, but when the color
leaves the Book
(as a return type) it should be wrapped with an Optional
.
return Optional.ofNullable(color); // inside the class
book.getColor().orElse("No color"); // outside the class
This provides clear design and more readable code.
1 If you intend for a BookWithColor
to encapsulate a whole "class" of books which have specialized capabilities over other books, then it would make sense to use inheritance.
-
1Who said anything about a database? If all OO patterns had to fit into a relational database paradigm, we would have to change a lot of OO patterns.alexanderbird– alexanderbird2016年05月02日 05:28:28 +00:00Commented May 2, 2016 at 5:28
-
I would argue adding adding unused properties "just in case" is the least clear option. As others said, it depends on the use case but the most desirable situation would be not to carry around properties where an external application needs to know whether or not a property that exists is actually being used.Volker Kueffel– Volker Kueffel2016年05月02日 18:35:33 +00:00Commented May 2, 2016 at 18:35
-
@Volker Let's agree to disagree, because I can cite several people who played a hand in adding the
Optional
class to Java for this exact purpose. It might not be OO, but it's certainly a valid opinion.castletheperson– castletheperson2016年05月02日 18:58:16 +00:00Commented May 2, 2016 at 18:58 -
@Alexanderbird I was specifically tackling a JavaBean, which should be serializeable. If I was off topic by bringing up JavaBeans, then that was my mistake.castletheperson– castletheperson2016年05月02日 19:00:01 +00:00Commented May 2, 2016 at 19:00
-
@Alexanderbird I don't expect all patterns to match a relational database, but I do expect a Java Bean tocastletheperson– castletheperson2016年05月02日 20:58:35 +00:00Commented May 2, 2016 at 20:58
You should either use an interface (not inheritance) or some kind of property mechanic (maybe even something that works like the resource description framework).
So either
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
or
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 {
}
Clarification (edit):
Simply adding optional fields in the entity class
(削除) Especially with the Optional-wrapper (削除ここまで)(edit: see 4castle's answer) I think this (adding fields in the original entity) is a viable way to add new properties in a small scale. The biggest problem with this approach is that it might work against low coupling.
Imagine your book class is defined in a dedicated project for your domain model. Now you add another project that uses the domain model to do a special task. This task requires an additional property in the book class. Either you end up with inheritance (see below) or you have to change the common domain model to make the new task possible. In the latter case you may end up with a bunch of projects that all depend on their own properties added to the book class while the book class itself in a way depends on these projects, because you are not able to understand the book class without the additional projects.
Why is inheritance problematic when it comes to a way to provide additional properties?
When I see your example class "Book" I think about a domain object that often ends up having many optional fields and subtypes. Just imagine you want to add a property for books that include a CD. There are now four kinds of books: books, books with color, books with CD, books with color and CD. You cannot describe this situation with inheritance in Java.
With interfaces you circumvent this issue. You can easily compose the properties of a specific book class with interfaces. Delegation and composition will make it easy to get exactly the class you want. With inheritance you usually end up with some optional properties in the class that are only there because a sibling class needs them.
Further reading why inheritance is often a problematic idea:
Why is inheritance generally viewed as a bad thing by OOP proponents
JavaWorld: Why extends is evil
The problem with implementing a set of interfaces to compose a set of properties
When you use interfaces for extension everything is fine as long as you have just a small set of them. Especially when your object model is used and extended by other developers e.g. in your company the amount of interfaces will grow. And eventually you end up creating a new official "property-interface" that adds a method your colleagues already used in their project for customer X for a completely unrelated use case - Ugh.
edit: Another important aspect is mentioned by gnasher729. You often want to dynamically add optional properties to an existing object. With inheritance or interfaces you would have to recreate the whole object with another class which is far from optional.
When you expect extensions to your object model to such an extent you will be better off by explicitly modelling the possibility of dynamic extension. I propose something like above where each "extension" (in this case property) has its own class. The class works as namespace and identifier for the extension. When you set the package naming conventions accordingly this way allows an infinite amount of extensions without polluting the namespace for methods in the original entity class.
In game development you often encounter situations where you want to compose behavior and data in many variations. This is why the architecture pattern entity-component-system got pretty popular in game development circles. This is also an interesting approach you might want to look at, when you expect many extensions to your object model.
-
And it hasn't addressed the question "how to decide between these options", it's just another option. Why is this always better than all of the options the OP presented?alexanderbird– alexanderbird2016年05月02日 05:32:40 +00:00Commented May 2, 2016 at 5:32
-
You are right, I will improve the answer.chromanoid– chromanoid2016年05月02日 20:28:03 +00:00Commented May 2, 2016 at 20:28
-
@4castle In Java interfaces are not inheritance! Implementing interfaces is a way to comply with a contract while inheriting a class is basically extending its behavior. You can implement multiple interfaces while you cannot extend multiple classes in one class. In my example you extend with interfaces while you use inheritance to inherit the behavior of the original entity.chromanoid– chromanoid2016年05月02日 21:46:41 +00:00Commented May 2, 2016 at 21:46
-
Makes sense. In your example,
PropertiesHolder
would probably be an abstract class. But what would you suggest be an implementation forgetProperty
orgetPropertyValue
? The data still has to be stored in some kind of instance field somewhere. Or would you use aCollection<Property>
to store the properties instead of using fields?castletheperson– castletheperson2016年05月02日 22:34:07 +00:00Commented May 2, 2016 at 22:34 -
1I made
Book
extendingPropertiesHolder
for reasons of clarity. ThePropertiesHolder
usually should be a member in all classes that need this functionality. The implementation would hold aMap<Class<? extends Property<?>>,Property<?>>
or something like this. This is the ugly part of the implementation but it provides a really nice framework that even works with JAXB and JPA.chromanoid– chromanoid2016年05月02日 22:37:58 +00:00Commented May 2, 2016 at 22:37
If Book
class is derivable without color property like below
class E_Book extends Book{
private String type;
}
then inheritance is reasonable.
-
I'm not sure I understand your example? If
color
is private then any derived class shouldn't have to be concerned withcolor
anyway. Just add some constructors toBook
that ignorecolor
altogether and it will default tonull
.castletheperson– castletheperson2016年05月02日 01:38:49 +00:00Commented May 2, 2016 at 1:38