30 October 2008

When to use Interfaces

The simplest answer: never. Use Ruby or any other dynamic language instead! But if you're still stuck in javaland, well, I promised this article in Programming for the Stone Age after witnessing some serious interface-itis. My university Software Engineering courses all talked about "always code to an interface" and articles abound on this insidious meme. So you get interface-implementation couples, where every method signature on the class is duplicated on the interface, pointlessly. I remember with horror a proprietary ORM I was obliged to use that actually required this pairing for every persistent class. It was unfortunately the backbone of our application, and hurt more than raw SQL. But that was in the days before Hibernate and the whole POJO movement, so let's call it a learning experience. Let us end the tedium of obligatory signature copying for every little property we add to our applications!

Right. So you're building your application, you decide your domain objects will include Customer, Supplier, Product(*). You already have a concept: a Domain Object, a kind of a thing that is unlike a Controller, or Service, or a Repository. A Domain Object has nothing in common with a HashMap or String or JdbcConnection. Major distinguishing features of Domain Objects are that each instance can be uniquely identified from among all Domain Object instances; each Domain Object is subject to CRUD operations and may be in an unsaved or deleted state. You might have decided for your application that objects are never deleted from your database, but rather they are tagged as deleted. You might decide that all domain objects should have a human-readable name. If any of this is the case, it is possible, depending on the nature of your application, that an interface resembling the following may be useful:

DomainObject.java
interface DomainObject {
  UniqueIdentifier getUID(); // this isn't necessarily the database id of the object
  String getName();
  boolean isSaved();
  boolean isDeleted();
}

With this interface in place, you can write some methods whose behaviour will be consistent for all of your domain objects:


  public void update(DomainObject o) {
    if (o.isDeleted()) {
      throw new CantUpdate("object " + o.getUID() + " is already deleted");
    }

    ...
  }

  public String renderLink(DomainObject o) {
    return "<a href='/object/" + o.getUID() + "'>" + o.getName() + "</a>";
  }

Customer and Supplier have a feature in common: they are contactable. That is, they have addresses, phone numbers, emails and so on. It would be nice to handle contact information in a uniform way: to do so, the Contactable interface might be handy:

Contactable.java
interface Contactable {
  String getAddress();
  void setAddress(String address);
  String getEmail();
  void setEmail(String email);

  ... etc ...
}

Armed with Contactable, you can manage contact information in a uniform way:


  public void verify(Contactable c) {
    ...
  }

  public String renderContactInfo(Contactable c) {
    ...
  }

Last example: Auditable : for those Domain Objects for which all changes need to be audited. If your application requires all objects are audited, you might as well merge this with DomainObject (and you might make it a superclass rather than an interface).

Auditable.java
interface Auditable {
  User modifiedBy();
  Date modifiedOn();
  void updated(User by, Date when);
}

Auditable works well in combination with a Hibernate interceptor or event handler so that every object that is persisted and requires auditing, is guaranteed to be audited with no more effort than adding "implements Auditable" to your class definition. This way, together with the magic of hibernate, your business-logic code can deal with modifying your object in a clear and readable fashion, without a heap of audit-code clutter. For example:

MyHibernateInterceptor.java
class MyHibernateInterceptor {
  boolean onSaveDirty(Object o, Stuff... otherStuff) {
    if (o instanceof Auditable) {
      ((Auditable) o).updated(getUser(), new Date());
    }

    ... etc ...
  }

  ... etc ...
}

So you end up with domain classes looking a little like this:

Customer.java
class Customer implements DomainObject, Auditable, Contactable {

  ... interface methods ...

  ... other methods unique to customer ...
}

Java interfaces allow you slice up a big class into smaller, managable chunks so that you can write well-targeted pieces of code that guarantee consistent behaviour over a range of different objects.

Lastly and most importantly: don't create the interface until you really need it. If you have only one implementation, and there is only one possible implementation (at least for the moment), then you don't need an interface. The day you have a second implementation of the same concept, hit Refactor -> Extract Interface in Intellij (**) to introduce your interface quickly and painlessly. And I really mean painlessly. Intellij will hunt down all references to the old class in your code, and convert them where possible into references to the interface. And you get to decide whether the class keeps its name and the interface gets a new name, or the other way around.

There is a whole universe of refactorings out there: "extract interface" is so trivial and easy to understand that it can be automated. So use it. When you have the freedom to evolve your code in any direction at the last minute, you implicitly have the freedom to keep your codebase much simpler by not evolving it until the last minute.

Good luck, and let me know how it goes.

(*) It looks like e-commerce is the "hello world" of web-app bloggers (**) I'm sure Eclipse and Netbeans have this refactoring too.

1 comment:

  1. What? You are still doing object oriented programming? ;-)

    ReplyDelete