Table of contents
The word specification means a rule.
Specification design pattern in java is used to check if an object meets a particular specification ( rule ).
An object can meet more than one specification.
If we are having two specifications, we can check if an object meets both specifications. We can also check if the object meets one of the specifications or none of the specifications.
The idea behind the Specification design pattern
Say we have to create a method that checks if a person is eligible to vote. If the person’s age is greater than or equal to 18 then return true else return false.
boolean isEligibleToVote(Person person) { boolean isEligible = false; if (person.getAge() >= 18) { isEligible = true; } return isEligible; }
This is the idea behind the specification design pattern. If the object meets certain criteria then the output is successful else it’s a failure. In the above example, we are checking whether the Person object meets the voting eligibility criteria or not.
Programmatic approach
We will create an interface say ISpecification , which contains a set of methods :
public interface ISpecification { boolean isSatisfiedBy(Object candidate); ISpecification and(ISpecification other); ISpecification or(ISpecification other); }
isSatisfiedBy method Checks if the object meets the rule
and method says if the object meets both the rules then return true else false.
or method says if the object meets one of the rules then return true else false.
Now we will make an abstract class to implement the above interface.
public abstract class AbstractSpecification implements ISpecification { public abstract boolean isSatisfiedBy(Object candidate); public ISpecification and(ISpecification other) { return null; } public ISpecification or(ISpecification other) { return null; } }
and method is returning null
or method both is returning null
Hence we should give a proper implementation to both methods.
And Specification
Create a class AndSpecification ( This class extends AbstractSpecification and overrides isSatisfiedBy method )
public class AndSpecification extends AbstractSpecification { private ISpecification specification1; private ISpecification specification2; public AndSpecification(ISpecification specification1, ISpecification specification2) { this.specification1 = specification1; this.specification2 = specification2; } @Override public boolean isSatisfiedBy(Object candidate) { // Check if the object meets both the specifications return specification1.isSatisfiedBy(candidate) && specification2.isSatisfiedBy(candidate); } }
Or Specification
We will create a class OrSpecification ( This class extends AbstractSpecification and override isSatisfiedBy method )
public class OrSpecification extends AbstractSpecification { private ISpecification specification1; private ISpecification specification2; public OrSpecification(ISpecification specification1, ISpecification specification2) { this.specification1 = specification1; this.specification2 = specification2; } @Override public boolean isSatisfiedBy(Object candidate) { // Check if the object meets one of the specifications return specification1.isSatisfiedBy(candidate) || specification2.isSatisfiedBy(candidate); } }
Now the AbstractSpecification class should return AndSpecification and OrSpecification instances.
and method should return the AndSpecification instance.
or method should return the OrSpecification instance.
public abstract class AbstractSpecification implements ISpecification { public abstract boolean isSatisfiedBy(Object candidate); public ISpecification and(ISpecification other) { return new AndSpecification(this, other); } public ISpecification or(ISpecification other) { return new OrSpecification(this, other); } }
Real-world example
Say we want to get a Driving license. So we need to provide the name and address details to the RTO authority.
We will create 2 classes say Name.java and Address.java.
Name.java public class Name { private String firstName; private String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } }
Address.java public class Address { private String city; private String country; public Address(String city, String country) { this.city = city; this.country = country; } public String getCity() { return city; } public String getCountry() { return country; } }
We will embed both the Name and Address class in a third-class say PersonalInformation.java.
Note* As I have mentioned earlier that we will check multiple specifications on a single object. Hence we are embedding Name and Address in PersonalInformation class.
PersonalInformation.java public class PersonalInformation { Name name; Address address; public PersonalInformation(Name name, Address address) { this.name = name; this.address = address; } public Name getName() { return name; } public Address getAddress() { return address; } }
Now we will create the Name specification and Address specification.
NameSpecification.java public class NameSpecification extends AbstractSpecification { @Override public boolean isSatisfiedBy(Object candidate) { System.out.println("isSatisfiedBy of NameSpecification"); PersonalInformation personalInformation = (PersonalInformation) candidate; if (personalInformation.getName() != null && (personalInformation.getName().getFirstName() != null && personalInformation.getName().getLastName() != null)) { return true; } return false; } }
Criteria to match NameSpecification: The user has to provide firstName and lastName. If either of them is not provided then NameSpecification is not satisfied.
AddressSpecification.java public class AddressSpecification extends AbstractSpecification { @Override public boolean isSatisfiedBy(Object candidate) { System.out.println("isSatisfiedBy of AddressSpecification"); PersonalInformation personalInformation = (PersonalInformation) candidate; if (personalInformation.getAddress() != null && (personalInformation.getAddress().getCity() != null && personalInformation.getAddress().getCountry() != null)) { return true; } return false; } }
Criteria to match AddressSpecification: The user has to provide city and country. If either of them is not provided then AddressSpecification is not satisfied.
Now for a User to get a driving license NameSpecification and AddressSpecification both should meet. Hence we will use “and method” from AbstractSpecification.
DrivingLicense.java public class DrivingLicense { public static void main(String[] args) { Name name = new Name("FName", "LName"); Address address1 = null; PersonalInformation personalInformation = new PersonalInformation(name, address1); NameSpecification nameSpecification = new NameSpecification(); AddressSpecification addressSpecification = new AddressSpecification(); boolean andSpecifiedWithNullAddress = nameSpecification .and(addressSpecification) .isSatisfiedBy(personalInformation); System.out.println("andSpecified with null address is : " + andSpecifiedWithNullAddress); Address address = new Address("City", "India"); PersonalInformation personalInformation1 = new PersonalInformation(name, address); boolean andSpecifiedWithAddress = nameSpecification .and(addressSpecification) .isSatisfiedBy(personalInformation1); System.out.println("andSpecified with address is : " + andSpecifiedWithAddress); } }
Output: In the above DrivingLicense class we covered 2 scenarios
1 ) When the Null address is provided
Output: isSatisfiedBy of AndSpecification
isSatisfiedBy of NameSpecification
isSatisfiedBy of AddressSpecification
andSpecified with null address is: false
2 ) When the address is provided
Output: isSatisfiedBy of AndSpecification
isSatisfiedBy of NameSpecification
isSatisfiedBy of AddressSpecification
andSpecified with address is: true
Specification design pattern Cases
AndSpecification: If we are providing the address object as null, in this case, AndSpecification fails.
Name name = new Name("FName", "LName"); Address address = new Address("City", null); PersonalInformation personalInformation = new PersonalInformation(name, address); boolean andSpecifiedWithAddress = nameSpecification .and(addressSpecification) .isSatisfiedBy(personalInformation); System.out.println("andSpecified with address but country null is : " + andSpecifiedWithAddress); Output : andSpecified with address but country null is : false
OrSpecification: If we are providing the address object as null, in this case, OrSpecification passes.
Name name = new Name("FName", "LName"); Address address = null; PersonalInformation personalInformation = new PersonalInformation(name, address); boolean orSpecified = nameSpecification .or(addressSpecification) .isSatisfiedBy(personalInformation); System.out.println("orSpecified is : " + orSpecified); Output : orSpecified is : true
This is all about the specification design pattern in java. If you like this article please share it. If you want to add anything extra please comment below and write to me if I have gone wrong anywhere.