12

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?

  • 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 1 '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 1 '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 1 '16 at 21:55
  • 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 1 '16 at 23:28
  • 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 2 '16 at 12:38
7

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 May 2 '16 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 May 2 '16 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. – 4castle May 2 '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 3 '16 at 5: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 3 '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").

  • This is really an important point! It defines whether one should consider a collection of optional properties instead of class attributes. – chromanoid May 4 '16 at 8: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.

  • 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 2 '16 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 May 2 '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 2 '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 2 '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 2 '16 at 20:58
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.

  • 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 2 '16 at 5:32
  • You are right, I will improve the answer. – chromanoid May 2 '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 2 '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<Property> to store the properties instead of using fields? – 4castle May 2 '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<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 May 2 '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 2 '16 at 1:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.