DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • Understanding Java Signals

Trending

  • AI, ML, and Data Science: Shaping the Future of Automation
  • Scaling Mobile App Performance: How We Cut Screen Load Time From 8s to 2s
  • AI Meets Vector Databases: Redefining Data Retrieval in the Age of Intelligence
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  1. DZone
  2. Coding
  3. Java
  4. How to Use the Specification Pattern in Java

How to Use the Specification Pattern in Java

Want to learn more about implementing the Specification pattern into your Java applications? Check out this post to learn more about Specifications.

By 
Hicham Layadi user avatar
Hicham Layadi
·
Updated Oct. 23, 18 · Tutorial
Likes (23)
Comment
Save
Tweet
Share
42.0K Views

Join the DZone community and get the full member experience.

Join For Free

This blog post is mainly inspired by the works of Martin Fowler and Eric Evans in their seminal paper Specifications

I find this pattern interesting as it remedies years of head bashing and hair pulling for finding ways of isolating the evolution of a domain model from the mechanisms involved in inspecting and querying it while maintaining a high level of encapsulation. I like to think of it as a "reflection" on the domain model. Hopefully, things will become clearer as we proceed.

Let us suppose that we have an e-commerce application with a very exotic product catalog where products are represented by a type hierarchy, for example:

public class Product{

String gTIN;

double price;

int units;

}

public class Television extends Product{

ResolutionEnum resolution;

float screenSize; //in inches

}


Suppose we'd like to search for products matching the following criteria:

  1. The maximum price is 3000 Dh (that is the Moroccan currency dirhams, MAD)
  2. The screen size is more than 12.1''

A naive approach would simply have it done this way:

public class SearchProductService{

ProductRepository inMemoryRepository;

public Product findBy(double maxPrice, float minScreenSize){

List<Product> allProducts = inMemoryRepository.findAll(p-> {p.price < price});

allProduct.stream().filter(p -> p instanceof Television).map( p-> (Television ) p)

.filter(p-> p.resolution > minScreenSize).collect(toList());
}
}


Now, suppose we also want to be able to search by resolution and available units. It won't get any better:

public class SearchProductService{

ProductRepository inMemoryRepository;

public Product findBy(double minPrice, float maxScreenSize ){

}


public Product findBy(ResolutionEnum resolution, int availableUnits ){

}

public Product findBy(ResolutionEnum resolution, int availableUnits ,double maxPrice, float minScreenSize){

}

//handle all combinations?

}


Clearly, this solution does not scale; plus, it forces us to expose some properties of the product and, maybe, some that we'd rather keep private. What is even worse is the logic that builds up upon these properties far away from where the properties are located. By all design standards, this solution is a time bomb and a maintenance nightmare. Can we do better?

Of course, we can — we wouldn't be here otherwise. The solution has been formalized under the name: specification pattern. Basically, we create our search criteria and let the product tell us if it meets them or not. Quoting the master Martin Fowler, "Tell, don't ask."

class Product{
public boolean satisfies(SearchCriteria criteria){

//Open up for extension

return criteria.isSatisifiedBy(this);

}
}

class Television extends Product{

//No change here }


/*************** Define Criteria ***********************/

public interface SearchCriteria{

boolean isSatisfiedBy(Product product);

}


/**************** Composite **************************/

public class Criteria implements SearchCriteria{

private List<SearchCriteria> criteria ;

public Criteria(List<SearchCriteron> criteria){

this.criteria = criteria;

}


//We could also add the operators add, not, or for combining criteria, here it is AND

public isSatisfiedBy(Product product)(){

Iterator<Criteria> iterator = criteria.iterator();

while(iterator.hasNext()){

if(!iterator.next().isSatisfiedBy(product))

return false;

}

return true:

}

}


/**************** Price Criterion **************************/

public class PriceCriterion implements SearchCritera{

public PriceCriterion(Operator operator, double target){

//

}

public boolean isSatisfiedBy (Product product){

//Put logic here

}

}


/*************** Criteria builder ********************/

public class SearchCriteriaBuilder{

protected List<SearchCriteron> criteria = new ArrayList<>();

private PriceCriteriaBuilder priceCriteriaBuilder;

public PriceCriteriaBuilder withPrice(){

if(priceCriteriaBuilder == null)

priceCriteriaBuilder = new PriceCriteriaBuilder();

return priceCriteriaBuilder;

}


public PriceCriteriaBuilder and(){

return this;

}


public SearchCritera build(){

return new Criteria(criteria);

}


public PriceCriteriaBuilder{

Operator operator;

double targetPrice;

public enum Operator{

lessThan,

equal,

largetThan

....

}


public PriceCriteriaBuilder being(Operator operator) {

this.operator = operator;

return this;

}


public PriceCriteriaBuilder value(double targetPrice) {

this.targetPrice = targetPrice;

PriceCriteriaBuilder.this.criteria.add(new PriceCriterion(operator,targetPrice));

return PriceCriteriaBuilder.this ;

}

}

}


Phew! With all of this behind us, let's look at what the client code would look like:

Criteria criteria = new SearchCriteriaBuilder().

withPrice()

.being(lessThan).value(3000)

.and()

.withScreenSize()//could be impletement in the same manner

.being(largetThan).value(12.1)

.build();

Television television = ProductRepository.getTelevisions(); television.satisfies(criteria);


Please admire the conciseness and expressivity of the solution. It is true that we had to put in a lot of code , but that's the price you have to pay if you're willing to use the porcelain instead of the plumbing. Please note that this implementation of the specification pattern is a mashup of several design patters:

  • Composite Pattern: The product deals only with the criteria class, and it does not change if there is one criterion or many.
  • Visitor Pattern: Each Concertecriteria class has to deal with the specifics of the product, and the interaction with the product is kept simple thanks to one method  satisfies(SearchCriteria criteria) .
  • Command Pattern: We build the list of criteria and execute them once at the end with a single call  satisfies(criteria).
  • Builder Pattern: Allowed the creation of a fluid, progressive API.

I hope that was an interesting read! Feel free to leave a note in the comments below!

Specification pattern Java (programming language)

Published at DZone with permission of Hicham Layadi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • Understanding Java Signals

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!