Monday, November 06, 2006

Domain Abstractions : Abstract Classes or Aspect-Powered Interfaces ?

In my last post I had discussed about why we should be using abstract classes instead of pure Java interfaces to model a behavior-rich domain abstraction. My logic was that rich domain abstractions have to honor various constraints, which cannot be expressed explicitly by pure interfaces in Java. Hence leaving all constraints to implementers may lead to replication of the same logic across multiple implementations - a clear violation of the DRY principle.

However, lots of people expressed their opinions through comments in my blog and an interesting discussion on the Javalobby forum, where I found many of them to be big proponents of *pure* Java interfaces. All of them view Java interfaces as contracts of the abstraction and would like to undertake the pain of extreme refactoring in order to accomodate changes in the *published* interfaces. This post takes a relook at the entire view from the world of interfaces and achieve the (almost) same effect as the one described using abstract classes.

Interfaces Alone Don't Make the Cut!

Since pure Java interfaces cannot express any behavior, it is not possible to express any constraint using interfaces alone. Enter aspects .. we can express the same behavioral constraints using aspects along with interfaces.

Continuing with the same example from the previous post :-

The interface ..


interface IValueDateCalculator {
  Date calculateValueDate(final Date tradeDate)
      throws InvalidValueDateException;
}



and a default implementation ..


public class ValueDateCalculator implements IValueDateCalculator {
  public Date calculateValueDate(Date tradeDate)
      throws InvalidValueDateException {
    // implementation
  }
}


Use aspects to power up the contract with mandatory behavior and constraints ..


public aspect ValueDateContract {

  pointcut withinValueDateCalculator(Date tradeDate) :
      target(IValueDateCalculator) &&
      args(tradeDate) &&
      call(Date calculateValueDate(Date));

  Date around(Date tradeDate) : withinValueDateCalculator(tradeDate) {

    if (tradeDate == null) {
      throw new IllegalArgumentException("Trade date cannot be null");
    }

    Date valueDate = proceed(tradeDate);

    try {
      if (valueDate == null) {
        throw new InvalidValueDateException(
          "Value date cannot be null");
      }

      if (valueDate.compareTo(tradeDate) == 0
        || valueDate.before(tradeDate)) {
        throw new InvalidValueDateException(
          "Value date must be after trade date");
      }

    } catch(Exception e) {
      // handle
    }

    return valueDate;
  }
}


Is this Approach Developer-friendly ?

Aspects, being looked upon as an artifact with the *obliviousness* property, are best kept completely decoupled from the interfaces. Yet, the complete configuration of a contract for a module can be known only with the full knowledge of all aspects that weave together with the interface. Hence we have the experts working on how to engineer aspect-aware-interfaces as contracts for the modules of a system, yet maintaining the obliviousness property.

While extending from an abstract class, the constraints are always localized within the super class for the implementer, in this case, the aspects, which encapsulate the constraints, may not be "textually local". Hence it may be difficult for the implementer to be aware of these constraints without strong tooling support towards this .. However, Eclipse 3.2 along with ajdt shows all constraints and advices associated with the aspect :

at aspect declaration site ..



and at aspect call site ..



And also, aspects do not provide that much fine grained control over object behavior than in-situ Java classes. You can put before(), after() and around() advices, but I still think abstract classes allow a more fine grained control over assertion, invariant and behavior parameterization.

Dependencies, Side-effects and Undocumented Behaviors

In response to my last post, Cedric had expressed the concern that with the approach of modeling domain abstractions using abstract classes with mandatory behavioral constraints

Before you know it, your clients will be relying on subtle side-effects and undocumented behaviors of your code, and it will make future evolution much harder than if you had used interfaces from the start.


I personally feel that for mandatory behaviors, assertions and invariants, I would like to have all concrete implementations *depend* on the abstract class - bear in mind that *only* those constraints go to the abstract class which are globally true and must be honored for all implementations. And regarding unwanted side-effects, of course, it is not desirable and often depends on the designer.

From this point of view, the above implementation using interfaces and aspects also suffer from the same consequences - implementers depend on concrete aspects and are always susceptible to unwanted side-effects.

Would love to hear what others feel about this ..

6 comments:

Ricky Clarkson said...

Suppose you have a simple class/interface that wraps an int, but only allows positive ints.

interface Positive
{
 int getInt();
 void setInt(int i);
}

Of course, you can't make implementations of this interface do what you want them to. You could just implement this as a final class instead and get rid of the interface.

final class Positive
{
 private int value;

 public int getInt()
 {
  return value;
 }

 public void setInt(int i) throws SomeException
 {
  if (i<0)
   throw new SomeException();

  value=i;
 }
}

So, these are the two extremes. Suppose you want to allow external implementation of part of a class. The traditional approach is the abstract superclass, but that's not the only way.

You can combine an interface with a final class - suppose I renamed the interface above to Number (seeing as it can't enforce positivity there's no point calling it Positive).

The Positive class could compose a Number instance and delegate calls through to it after applying its own checking.

Obviously in this tiny case, this isn't so useful, hopefully next time I post about this somewhere I'll have a better example.

class Positive
{
 private final Number number;

 public Positive(Number number)
 {
  this.number=number;
 }

 public int getInt()
 {
  return number.getInt();
 }

 public void setInt(int i) throws SomeException
 {
  if (i<0)
   throw new SomeException();

  number.setInt(i);
 }

 public int getInt()
 {
  return number.getInt();
 }
}

Kevin O'Neill said...

Actually I wrote about using aspectj for interface assertions earlier this year while exploring various methods of enforcing pre and post conditions on interface methos.

Colin Jack said...

I think the positive number example is a good one, to me the Positive class (value object) is far more intention revealing than interface + aspects will ever be.

In my view we need to have really good reasons for taking this approach when it is adding complexity to our domain model in the process, and I don't see what the problem with using concrete classes here is. What problem are we trying to solve?

The thread in the DDD forum, which I have not yet had time to read, may answer this question.....

Debasish said...

@Colin:
For value objects like Positive, it makes perfect sense to write final classes that abstract the values and expose the properties as final methods. Personally I am fully +1 with you on this. Only if the entity has multiple implementations in the domain, then we should go for interfaces. Aspects add to the complexity, hence, in my previous post, I had suggested using abstract classes that nicely enforce domain level contracts and assertions.

Colin Jack said...

Cool, definitely a good post and an interesting topic.

Ashkan said...

What about ease of testing with interfaces? I only need to ensure that my designed interfaces are not easy to test in order to refactor them before any implementation. Of course I mock them but it is cheap.