Friday, October 5, 2007

Strategy and Decorator - A comparison (Part 1)

Just the other day, while working on a project, I had the opportunity to see first hand how the Strategy and Decorator patterns handle similar cases, but with very different results and methods. To summarize (and my apologies to the GoF and all other authorities on such subjects), the Decorator wraps an object (creating a has-a relationship) and extends functionality in one of a few ways. The Strategy pattern, on the other hand, allows one to swap out of the guts of an object, usually the implementation of a specific method or algorithm, for another, dynamically.

In this entry, I'll take a look at the Decorator pattern, an example, and what makes it cool.

The Decorator is neat because you can simply nest decorators infinitely, delegating methods that aren't interesting to extend to the inner object. All decorators must implement the interface of the object they decorate so that users of decorated objects don't notice a difference and find all the methods they expect. If this looks a lot like inheritance, that's because it is (albeit very loosely coupled inheritance). Clearly there are upsides and downsides to the Decorator pattern - classes become loosely coupled (we like that), but sometimes you can wind up with a system with lots of little objects that are hard to debug (we don't like that). Different languages can accomplish this pattern in different ways, but an obvious method is to do something like the following.

/*
 * The interface our objects must implement.
 */
public interface GenericValidator {
  public void validate(Object value) throws InvalidValueException;
}

/*
 * A string validator
 */
public class StringValidator implements GenericValidator {
  public void validate(Object value) throws InvalidValueException {
    // ...
  }
}

/*
 * A length validator. This assumes that the value to validate
 * also conforms to some kind of interface. In this case, we guess
 * that it supports the method getLength(). This, of course, won't
 * actually work because the Object class doesn't support getLength()
 * and we don't do any casting (nor do we use generics which would be
 * even better).
 */
public class LengthValidator implements GenericValidator {
  private GenericValidator validator;
  private int maxLength;

  public void LengthValidator(GenericValidator validator) {
    this.validator = validator;
    maxLength = 50;
  }

  public void validate(Object value) throws InvalidValueException {
    if (value.getLength() > maxLength) {
      throw new InvalidValueException();
    }

    /* Invoke another validator if one exists */
    if (validator != null)
      validator.validate(value);
  }
}

In our example above, we have an interface that all validators must support - GenericValidator. This effectively gives us the same guarantee that inheritance does; that all objects will provide these methods. The difference here is that the StringValidator and LengthValidator are not related, at all, hierarchically. In fact, the LengthValidator could wrap up ArrayValidator to confirm that it fits within some given size constraints. This is where decorators get interesting.

The other point of note in this example is that of the value argument to the validate() method. This has nothing to do with the Decorator pattern, but it illustrates how one can write a generic utility to handle arbitrary types. Our example is fundamentally flawed (read: it doesn't really work) but that's due to my laziness and desire to not clutter the example with Java's generics which some non-Java / non-C++ folks may not be able to read. Let's pretend it does work. You get the point. In languages that favor the idea of so called duck typing, like Ruby, this kind of code not only works, but is desirable.

In practice, it is usually obvious when it makes sense to use Decorator rather than inheritance, for instance. Usually, there are a number of factors such as whether the relationships are dynamic or static, whether all or most of the combinations make sense, and if the number of subclass combinations would create an explosion of classes. It also depends on the features and behaviors of your language and what developers in that camp are prone to doing (i.e. best practices for you and them).

Next time, we'll take a similar look at the Strategy pattern. As always, I'm interested in comments.

No comments: