All Your Base Class Belong to Us

Back when I was a C++ programmer, I worked in an environment that stressed the importance of the const keyword. At any given time, we would have to understand whether or not the object we were referencing was mutable or not, and if not, we might have to make a copy (using our good friend, the copy constructor) before we changed its values.

Most of the little quibbles I had about the Java language have evaporated. Now that J2SDK 5.0 has included enums and type polymorphism (generics), I seldom find myself saying, “If only Java had…”.

But one thing I still wish Java had was a formal notion of mutability. I wish that it was possible to tell whether or not a particular object is mutable.

Often, I find myself making shortcuts that I never would have tolerated in C++. Like this:

import java.util.Date;

public class Policy {

  private Date effectiveDate;

  public void setEffectiveDate(Date effectiveDate) {
    this.effectiveDate = effectiveDate;
  }
...
}

In this example, the Java date classes are mutable, and hence, the reference inside of my Policy class is the same reference that was provided by my caller. That means that the caller can change the value of my policy’s effective date without calling any method on my Policy. And that breaks encapsulation.

The part of me that constantly thinks about const and mutability thinks that the setter should be written like this:

public void setEffectiveDate(Date effectiveDate) {
  if (effectiveDate != null) {
    this.effectiveDate = new Date(effectiveDate.getTime());
  } else {
    this.effectiveDate = null;
  }
}

or

public void setEffectiveDate(Date effectiveDate) {
  this.effectiveDate = effectiveDate == null
     ? null : new Date(effectiveDate.getTime());
}

Java has some weird implementations of immutability. One of my big “yuck” reactions is around the collections classes. Specifically, this kind of situation:

List validValues = Collections.unmodifiableList(anotherList);

What’s yucky about this set-up is that if I, later, call a method like add(Object) on that list of valid values, I get an UnsupportedOperationException. Some people say that that violates an object-oriented design principle called the Liskov Substitution Principle. The unmodifiable implementation of List does not adhere to the contract defined by the List API, and therefore it’s not a valid implementation.

I like this brief description of Liskov:

&#8220

The derived class must do what the base class promises, not what it actually does.

&#8221

Here’s an interesting read about how the Liskov Substitution Principle influences the question about whether or not a circle is a kind of ellipse (in the OO “ISA” sense). Those of us with any reasonable background in math have heard that a circle is a special case of ellipse, and so clearly a circle is a kind of ellipse. This particular quotation is interesting:

&#8220

It means your intuitive notion of “kind of” is leading you to make bad inheritance decisions. Your tummy is lying to you about what good inheritance really means — stop believing those lies.

[…] Your intuition is wrong, where “wrong” means “will cause you to make bad inheritance decisions in OO design/programming.”

Here’s how to make good inheritance decisions in OO design/programming: recognize that the derived class objects must be substitutable for the base class objects. That means objects of the derived class must behave in a manner consistent with the promises made in the base class’ contract.

&#8221

The Liskov Sustitution Principle is one of the design principles that Robert Martin discusses in his book, Agile Software Development, Principles, Patterns, and Practices. According to Martin, good agile developers always keep these principles in mind when writing code. He doesn’t really talk about how agile developers acquire this deep adherence to Liskov (except maybe by buying and reading his book), but he believes that these design principles are necessary for agile development to work.

What strikes me as interesting about Martin’s book (and the writings of a number of other good OO designers) is this: for them, subtyping is an exercise that follows certain rules, and Liskov is one of the rules that people try to apply.

But when I’m working on a project, what I tend to see is people who create common superclasses when they encounter bits of code that they want to reuse. It seems like the primary thing people think about when they start subclassing is “do I have code that I need to centralize in one spot?” It seems like a lot of people view inheritance as a reuse mechanism, and nothing more.

I worked with someone who once articulated this code smell: he said that if you’re creating a base super class and you can’t come up with a better name than “BaseSuchAndSuch”, maybe it’s not a valid superclass because it doesn’t represent a real domain construct. And maybe you should re-think your inheritance design.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply