I think this a very good idea.
Let us play a bit.
First of all, we need to download Apache Commons Collections and put its jar in our classpath.
Then, we need a little domain for our example ...
Say you have an university with a lot of courses: each Course is worth some credits.
You have also a lot of students: each Student can register an exam, related to a given course, and can request a graduation thesis.
At the moment, we know that the student can request the graduation thesis only if he/she has a minimum number of credits and if he/she has passed a minimum number of exams.
Perfect ... let's code our domain.
We surely have two entity objects: Course and Student.
Moreover, we have two rules defining when a student can successfully request a graduation thesis.
We must make this rules explicit in our domain, implementing them as Specifications.
We'll use the Predicate interface for implementing a Java generics based specification abstract base class:
import org.apache.commons.collections.Predicate;
public abstract class GBaseSpecification<T>
implements Predicate {
public boolean evaluate(Object object) {
return this.isSatisfiedBy((T) object);
}
public abstract boolean isSatisfiedBy(T object);
}
This class uses Java5 generics for letting concrete subclasses specifying the
right object type to use for evaluating the specification.
So, the generics method isSatisfiedBy(...), together with a meaningful name for the concrete subclass, allows us to implement a specification which adopts the domain specific language, rather than using an unexpressive evaluate(Object ) method.
Here are the two concrete specifications:
public class EnoughCreditsSpecification
extends GBaseSpecification<Student> {
private static final int min = 10;
public boolean isSatisfiedBy(Student s) {
if (s.getTotalCredits()>EnoughCreditsSpecification.min) {
return true;
}
else {
return false;
}
}
}
public class EnoughCoursesSpecification
extends GBaseSpecification<Student> {
private static final int min = 1;
public boolean isSatisfiedBy(Student s) {
if (s.getTotalCourses()>EnoughCoursesSpecification.min) {
return true;
}
else {
return false;
}
}
}
You can see that the two specifications meaningfully express the two domain concepts.
Next you'll have to implement Course (very trivial) and Student:
Take a look at Student's comments.public class Course {
private int credits;
public int getCredits() {
return credits;
}
public void setCredits(int credits) {
this.credits = credits;
}
}
import java.util.List;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
public class Student {
private int totalCourses;
private int totalCredits;
private List<GBaseSpecification<Student>> graduationSpecs;
/**
* Register an exam related to a given course.
* This increases the student credits.
*/
public void registerExam(Course course) {
this.totalCredits += course.getCredits();
this.totalCourses++;
}
/**
* Return true if this student can request a graduation
* thesis, depending on a given list of specifications.
*/
public boolean requestGraduationThesis() {
Predicate p= PredicateUtils.allPredicate(graduationSpecs);
return p.evaluate(this);
}
public int getTotalCredits() {
return this.totalCredits;
}
public int getTotalCourses() {
return this.totalCourses;
}
/**
* Get the list of specifications defining in which case
* a student can request a graduation thesis.
*/
public List<GBaseSpecification<Student>> getGraduationSpecs(){
return graduationSpecs;
}
/**
* Set the list of specifications defining in which case
* a student can request a graduation thesis.
*/
public void setGraduationSpecs(
List<GBaseSpecification<Student>> graduationSpecs) {
this.graduationSpecs = graduationSpecs;
}
}
First, notice the getter and setter methods which permit us to define a collection of GBaseSpecification objects for specifying graduation thesis request rules.
Next, notice how the requestGraduationThesis() is implemented: it uses the PredicateUtils class for combining Predicate objects, that is, our specifications!
Recall: our specifications implement the evaluate(Object ) method of the Predicate interface, so they can be combined by PredicateUtils!
In this example, with only one line of code we combine our specifications using the logical and operator.
Now, a simple main for testing our classes:
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
public class TestStarter {
public static void main(String[] args) {
Course c1 = new Course();
Course c2 = new Course();
c1.setCredits(5);
c2.setCredits(6);
Student s1 = new Student();
Student s2 = new Student();
List<GBaseSpecification<Student>> specs = new ArrayList();
specs.add(new EnoughCoursesSpecification());
specs.add(new EnoughCreditsSpecification());
s1.setGraduationSpecs(specs);
s2.setGraduationSpecs(specs);
s1.registerExam(c1);
s1.registerExam(c2);
s2.registerExam(c1);
System.out.println("Can s1 request a thesis? "
+ s1.requestGraduationThesis());
System.out.println("Can s2 request a thesis? "
+ s2.requestGraduationThesis());
}
}
You can obviously define a default set of specifications, set the specifications collection via dependency injection and so on ... this is only a simple use case.
Hope you've found this a simple and powerful way of implementing and combining specifications.
Take a look at the PredicateUtils javadoc ... and have fun!
7 comments:
Nice post (it's easy to say when I'm backlisted ;) )
I was discussing this morning with a coworker about your example and discussed about how to have a Student and a Role pattern and use it as an abstract factory for the different types of specifications you could attach to it. Nice food for thought...
Hi,
This is a good example how to implement specification with domain object.. hope can read more example from your site
Thanks for your kind comments, boys, hope to see more and more feedback.
Regards,
Sergio B.
Hi, Sergio
We have talked about implementing specifications in a more generic fashion in my project. We are currently using Hibernate Criteria.
However, I'd need to do SQL, HQL or EJBQL in order to search in the database. It seems like it would be possible to do this from Commons Collections Predicates.
Have you got any experience with this issue?
> We are currently using Hibernate
> Criteria.
> However, I'd need to do SQL, HQL or
> EJBQL in order to search in the
> database. It seems like it would be
> possible to do this from Commons
> Collections Predicates.
> Have you got any experience with
> this issue?
Sorry, I've no specific experience.
My opinion is you should abstract from the specific criteria language (sql, hql or so), in order to construct general criteria-style specifications.
You could implement generic criteria-style specifications which compare object properties by reflection, and then combine these criteria with Commons' Predicates ... however, you'll have IMHO to hand code the "translation" phase toward specific criteria language.
Regards,
Sergio B.
Thanks for the response, Sergio. We'll try it out and hopefully post our experience on the DDD mailing list.
Sergio,
If I call your method requestGraduationThesis and somehow it doesn't pass, since I want to display the appropriate message to UI, how do I know which specification that causes it to fail ?
Regards,
Setya
Post a Comment