ga('send', 'pageview');
Categories
Teknik

Specification Pattern as a Refactoring Tool

Specification is a tactical design pattern presented in Erics Evans’ book Domain-Driven Design. It can be used not only when new code is written, but also as a power tool for refactoring work. Patrik Fredriksson shows us how to use the Specification pattern to transform a piece of ugly code into a creation of beauty.

Specification is a tactical design pattern presented in Erics Evans’ book Domain-Driven Design. It can be used not only when new code is written, but also as a power tool for refactoring work. Patrik Fredriksson shows us how to use the Specification pattern to transform a piece of ugly code into a creation of beauty.

Eric Evan’s book on Domain-Driven Design contains a lot of good stuff. One of my favorite tactical design patterns is the Specifications pattern. It’s just a small piece of the puzzle but it is particularly nice since you can put it to use immediately, no matter where in the development life-cycle you are. It presents a great way of taking potentially ugly code that is hard to understand and encapsulate it in small crisp pieces of reusable business rules. 2007-05-06

Their limited scope makes specifications easy to test, and an elegant way of adding operations such as AND, OR and NOT makes it possible to combine these pieces into new rules that can be applied throughout the code. If the rules are given good names they will enable you to throw out hard to read code and replace with code that not only provides an elegant solution, but also makes the intent spelled out in natural language. If we implement the Specifications pattern with use of Java 5 generics we can make it even better. Let me show you.

Let’s say you have a collection of some sort, from which you’d like to filter out some of the elements based on different criteria. I see this kind of code all over the place in projects I have been involved in.

Imagine you run a used car dealership. Your particular niche is selling cars of the color red. But they must also be fairly new and reliable. So besides being red, you want the current owner to live in certain geographical regions, regions that provide good living conditions for cars (e.g. no winterish kind of places), and you want the cars to be less than five years old. But there is one exception, if it’s a convertible, the only restriction that applies is that it still must be red. You have written a small application to scan the Big National Repository of Used Cars for the ones that could be of interest to you. It could look something like this (if we for now pretend that we do not need the performance of a new eBay, and this way of scanning a collection is good enough):

 

public class CarServiceImpl implements CarService {

  private CarRepository repository;

  public void setRepository(final CarRepository repository) {
    this.repository = repository;
  }

  public Collection<Car> findCandidateCars() {

    final CalendarDate today = Clock.now().calendarDate(TimeZone.getTimeZone("GMT"));

    final Duration maxAge = Duration.years(5);

    final Collection<Car> cars = repository.findAllCarsInStock();
    final Collection<Car> keepers = new HashSet();

    final Set<Region> authorizedRegions = getAuthorizedRegions();

    for (Car car : cars) {
      if (car.color() == Color.RED &&
         (car.isConvertible() ||
            authorizedRegions.contains(car.owner().homeAddress().region()) &&
               maxAge.startingFrom(car.manufacturingDate()).includes(today)
         )
         )
        keepers.add(car);

    }
    return keepers;
  }

  private Set<Region> getAuthorizedRegions() {
    Set<Region> regions = new HashSet();
    regions.add(SOUTH_WEST);
    regions.add(SOUTH_EAST);
    regions.add(SOUTH);
    return regions;
  }
}  

Although this is a fairly simple and contrived example we can already see that this is not exactly easy to read, and as this code grows it will only get worse. The rules are welded in here, if you need to do the same, or similar, operations somewhere else, you would most probably just copy and paste this code. We also now have domain logic in the service, which is unnecessary and makes the whole thing a bit too secretive. We could push this code into the Car class perhaps, but that would probably cause more damage than do good to the Car. The business rules are just not as explicit as we want them to be. Enter Specification:

The Specification pattern in its core is simple, it consists of an interface with one method:

specifications_1

To reach our goals set up above, Evans extends it using the GoF Composition and Decorator patterns to give it some more operations:

specifications_2

 

We can implement this with one interface, one abstract class and three specialized classes, one each for the AND, OR, and NOT operations:

 

public interface Specification {

  boolean isSatisfiedBy(Object o);

  Specification and(Specification specification);

  Specification or(Specification specification);

  Specification not(Specification specification);
}


public abstract class AbstractSpecification implements Specification {

  public abstract boolean isSatisfiedBy(Object o);

  public Specification and(final Specification specification) {
    return new AndSpecification(this, specification);
  }

  public Specification or(final Specification specification) {
    return new OrSpecification(this, specification);
  }

  public Specification not(final Specification specification) {
    return new NotSpecification(specification);
  }
}


public class AndSpecification extends AbstractSpecification {

  private Specification spec1;
  private Specification spec2;

  public AndSpecification(final Specification spec1, final Specification spec2) {
    this.spec1 = spec1;
    this.spec2 = spec2;
  }

  public boolean isSatisfiedBy(final Object o) {
    return spec1.isSatisfiedBy(o) && spec2.isSatisfiedBy(o);
  }
}


public class OrSpecification extends AbstractSpecification {

  private Specification spec1;
  private Specification spec2;

  public OrSpecification(final Specification spec1, final Specification spec2) {
    this.spec1 = spec1;
    this.spec2 = spec2;
  }

  public boolean isSatisfiedBy(final Object o) {
    return spec1.isSatisfiedBy(o) || spec2.isSatisfiedBy(o);
  }
}


public class NotSpecification extends AbstractSpecification {

  private Specification spec1;


  public NotSpecification(final Specification spec1) {
    this.spec1 = spec1;
  }

  public boolean isSatisfiedBy(final Object o) {
    return !spec1.isSatisfiedBy(o);
  }
}

Now, lets see how we can implement

 

our rules. Implementing the color rule is straight-forward, like this:

public class CarColorSpecification extends AbstractSpecification {
  private Color color;

  public CarColorSpecification(Color color) {
    this.color = color;
  }

  public boolean isSatisfiedBy(Object o) {
    if (o instanceof Car) {
      Car car = (Car) o;
      return car.color() == color;
    } else {
      throw new ClassCastException("I only deal with cars, you gave me: " +
         o.getClass().getCanonicalName());
    }
  }
}

Our example now looks like this:

public class CarServiceImpl implements CarService {

  private CarRepository repository;

  public void setRepository(final CarRepository repository) {
    this.repository = repository;
  }

  public Collection<Car> findCandidateCars() {

    final Collection<Car> keepers = new HashSet<Car>();

    final CalendarDate today = Clock.now().calendarDate(TimeZone.getTimeZone("GMT"));

    final Specification approvedAge = new CarAgeSpecification(today, 5);
    final Specification colorRed = new CarColorSpecification(RED);
    final Specification convertible = new ConvertibleCarSpecification();
    final Specification approvedState = 
  new CarOwnerRegionSpecification(getAuthorizedRegions());

    final Specification candidateCarSpecification =     
             colorRed.and(approvedState.and(approvedAge).or(convertible));

    final Collection<Car> cars = repository.findAllCarsInStock();

    for (Car car : cars) {
      if (candidateCarSpecification.isSatisfiedBy(car))
        keepers.add(car);
    }

    return keepers;
  }

  private Set<Region> getAuthorizedRegions() {
    Set<Region> regions = new HashSet<Region>();
    regions.add(Region.SOUTH_WEST);
    regions.add(SOUTH_EAST);
    regions.add(SOUTH);

    return regions;
  }
}

Nice! This almost reads like natural language, the individual specifications are straightforward to test and the rules can easily be reused in other parts of the application. If we would like to make some or all of the rules externally configurable it is trivial to inject pre-configured specification instances via a dependency injection framework, like Spring. We may not end up with fewer lines of code, actually, counting the specification classes it’s certainly more lines, but I like this code a lot better than what we had before due to its clarity.

But we are not done yet. Let’s revisit the CarColorSpecification. This class looks quite good, but with a little help from Java 5 and Generics we can clean it up a bit. After updating the interface and classes to work with Generics, the specification can be written as:

public class CarColorSpecification extends AbstractSpecification<Car> {

  private Color color;

  public CarColorSpecification(Color color) {
    this.color = color;
  }

  public boolean isSatisfiedBy(final Car car) {
    return car.color() == color;
  }
}

Now we have compile-time type checking. We can get rid of that ugly instanceof cast, and also throw out some pointers in the Javadoc that now are explicitly stated in the method signature. The specifications are part of the domain, they are really specialized Value Objects, and by modifying our service just a little bit we can extract the specifications from the service implementation and make the service more general and the domain concepts even more explicit. The service will pull from the repository whatever we specify it to get:

public class CarServiceImpl implements CarService {

[...]

  /**
   * Check the national used car repository and find 
   * cars satistfying the given specification.
   *
   * @param specification Car specification.
   * @return Candidate cars.
   */
  public Collection<Car> findCandidateCars(Specification<Car> specification) {

    final Collection<Car> keepers = new HashSet();
    final Collection<Car> cars = repository.findAllCarsInStock();

    for (final Car car : cars) {
      if (specification.isSatisfiedBy(car))
        keepers.add(car);
    }
    return keepers;
  }

[...]

}


We could also adjust the CarRepository interface to accept the specification, arguing that this is really all about searching the repository for matching cars, and that is the responsibility of the CarRepository.

This pattern is nice to use when modeling and designing right from the start of a project, but as presented above it is also a nice tool for refactoring existing code.

For a more detailed discussion on the Specifications pattern, see Domain-Driven Design by Eric Evans. In the book you will also see examples of how we can express selection specifications, like the one described here, in a query language such as SQL, or in a combination of SQL and other languages. This will help performance, enabling us to actually use specifications for our next eBay application. You will also find examples of using the Specifications pattern for validation and creation, e.g. build me an instance of Car that fulfills this specification.

All the code above, complete with running scenario tests, is available for download at http://code.google.com/p/specification/. There you will also shortly find a packaged JAR-file with the generic specification classes above, ready for immediate use in your project!

Leave a Reply

Your email address will not be published. Required fields are marked *