8

Dedicated interfaces seems to be a good way to expose the optional features in a domain-specific type hierarchy. However, they impede the use of decorator and composite patterns, which is also common in this kind of hierarchy.

Especially, probably no one wants to implement a decorator/composite for each possible combination of these interfaces, so more often than not they implements all optional interfaces and use type checking to selectively forward the calls, which defeats the purpose of having separated interfaces.

An alternative, which the Java Collection framework uses, is to include all operations in the base type and just fill them with dummy implementations. Default methods in Java 8 further facilitates this usage.

In a way, this feels like the nullable columns debate in the database world. Is there any strong argument against the more practical latter approach?

  • Are you limited to Java or the JVM? Because Scala has this neatly solved – Frank Aug 28 '15 at 6:00
  • @Frank I would dream to be able to use Scala or any language that has traits, but unfortunately, using Java 8 is already a big leap for my company... – billc.cn Aug 28 '15 at 9:19
2

One solution to this problem I've used in the past (where a large number of decorators add facilities to a relatively simple base, with many possible interfaces) is to have a base class for both the decorators and the classes they wrap, regardless of interface they implement, that has a method declared (using Java's syntax):

    public <T> T findImplementation (Class<T> cls) {
       if (cls.isAssignableFrom(getClass())
           return cls.cast(this);
      return wrapped.findImplementation (cls);
   }

Obviously non-decorator implementations return null rather than passing the request on, as there's nowhere to pass it in this case.

  • Very intersting solution. (I eventually added a getWrappedInstance() method to my type hierarchy to do something similar. Wish I have thought of this.) – billc.cn Aug 30 '15 at 12:13
1

The challenge with the alternative is that it makes adding/changing/removing optional features more difficult. For example, if we add a new optional feature, we now have to change the base class, and then change all of the subclasses to add the feature or the non-feature. This can be somewhat mitigated if there is a reasonable default, such as a no-op, so only the subtypes that support the feature need to override the necessary parts. If these optional features are few, relatively stable, and unlikely to change, just adding them to the supertype could be sufficient. If not, then maintenance can start to get unwieldy.

The most common design pattern for recovering type from supertype is the visitor pattern. This might be tricky in your situation since a single object may implement multiple interfaces, but it could still be doable with some modifications.

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.