![]() |
|
|
|
| | | | | | | | | | |
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;
}
}
The Specification pattern in its core is simple, it consists of an interface with one method:


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);
}
}
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());
}
}
}
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;
}
}
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;
}
}
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;
}
[...]
}
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!
![]() |
Patrik Fredriksson is a senior consultant at, and one of the owners of, Citerus. His special interests are Java software design, architecture, and software development efficiency. |


Eric Evans and Patrik Fredriksson, March 11-14, 2008
Eric Evans, March 17-18, 2008![]() |
Ordet PNEHM! bildas av de värdeord Citerus konsulter vill bli förknippade med; prestigelöshet, nyfikenhet, engagemang, helhetssyn och mod. Utropstecknet står för en vilja att agera professionellt i alla lägen. |
![]() |