14

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?

4castle
  • 145
  • 10
  • 1
    I 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 May 01 '16 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 May 01 '16 at 21:23
  • 1
    If 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 May 01 '16 at 21:55
  • When you say `BookWithColor`, what does that even mean? Will it change the functionality of a normal `Book`? – 4castle May 01 '16 at 23:00
  • 4
    Can 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 May 01 '16 at 23:28
  • Looks like the comments currently have better answers than the actual answers.... @DavidPacker, will you post your comments as an answer? – alexanderbird May 02 '16 at 05:31
  • 1
    If the possible book colors are known ahead of time, how about an Enum field for BookColor with an option for BookColor.NoColor? – Graham May 02 '16 at 12:38
  • @BojanVukasovicTest - By specifying that there are explicitly books without color then you've answered your own question and eliminated your 2nd and 3rd examples from consideration. Also, you shouldn't need to use the first example usage code that you've shown. I agree with David and go a step further and say that the Liskov-Substitution Principle should always be a consideration. If you are violating it then assume your design is wrong. There are very few reasonable edge cases where there's a need to violate it. Your design will be vastly improved if you can adhere to it. – Dunk May 03 '16 at 21:51
  • @Graham That is option two with a slightly different implementation detail. (Not that it should be discounted given your proviso.) – Mark Hurd May 04 '16 at 04:15

5 Answers5

8

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.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • or you could bastract the weight to be able to reflect both, but that's probably too much. – Walfrat May 02 '16 at 07:14
  • 3
    @Walfrat: It would be a really bad idea to have the same field represent completely different things in different subclasses. – JacquesB May 02 '16 at 07: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. – 4castle May 02 '16 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 May 03 '16 at 05:52
  • 3
    agree 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 May 03 '16 at 10:11
5

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").

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • This is really an important point! It defines whether one should consider a collection of optional properties instead of class attributes. – chromanoid May 04 '16 at 08:29
1

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:

  1. Do not declare any instance variable of type Optional.
  2. Use null to indicate optional data within the private scope of a class.
  3. Use Optional for getters that access the optional field.
  4. Do not use Optional in setters or constructors.
  5. 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.

4castle
  • 145
  • 10
  • 1
    Who 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 May 02 '16 at 05: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 May 02 '16 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. – 4castle May 02 '16 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. – 4castle May 02 '16 at 19:00
  • @Alexanderbird I don't expect all patterns to match a relational database, but I do expect a Java Bean to – 4castle May 02 '16 at 20:58
  • Fair enough. How did you get to JavaBean? I don't see it in the question. @BojanVukasovicTest, should the solution follow the JavaBean standard? Is this relevant to your question? – alexanderbird May 03 '16 at 01:15
  • @alexanderbird I got to Java Bean because the OP doesn't suggest that the class is anything more than a class to encapsulate data. I could be wrong about that. Hopefully the information about `Optional` is still helpful anyway since they showed a little bit of misunderstanding of it in the question. – 4castle May 03 '16 at 01:22
0

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.

chromanoid
  • 195
  • 5
  • 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 May 02 '16 at 05:32
  • You are right, I will improve the answer. – chromanoid May 02 '16 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 May 02 '16 at 21:46
  • Makes sense. In your example, `PropertiesHolder` would probably be an abstract class. But what would you suggest be an implementation for `getProperty` or `getPropertyValue`? The data still has to be stored in some kind of instance field somewhere. Or would you use a `Collection` to store the properties instead of using fields? – 4castle May 02 '16 at 22:34
  • 1
    I made `Book` extending `PropertiesHolder` for reasons of clarity. The `PropertiesHolder` usually should be a member in all classes that need this functionality. The implementation would hold a `Map>,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 May 02 '16 at 22:37
-1

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 with `color` anyway. Just add some constructors to `Book` that ignore `color` altogether and it will default to `null`. – 4castle May 02 '16 at 01:38