Specification design pattern

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.

Leave a Comment