Files
java-design-patterns/specification/README.md
T
Ilkka Seppälä 6cd2d0353a docs: Content SEO updates (#2990)
* update yaml frontmatter format

* update abstract document

* update abstract factory

* use the new pattern template

* acyclic visitor seo

* adapter seo

* ambassador seo

* acl seo

* aaa seo

* async method invocation seo

* balking seo

* bridge seo

* builder seo

* business delegate and bytecode seo

* caching seo

* callback seo

* chain seo

* update headings

* circuit breaker seo

* client session + collecting parameter seo

* collection pipeline seo

* combinator SEO

* command seo

* cqrs seo

* commander seo

* component seo

* composite seo

* composite entity seo

* composite view seo

* context object seo

* converter seo

* crtp seo

* currying seo

* dao seo

* data bus seo

* data locality seo

* data mapper seo

* dto seo

* decorator seo

* delegation seo

* di seo

* dirty flag seo

* domain model seo

* double buffer seo

* double checked locking seo

* double dispatch seo

* dynamic proxy seo

* event aggregator seo

* event-based asynchronous seo

* eda seo

* event queue seo

* event sourcing seo

* execute around seo

* extension objects seo

* facade seo

* factory seo

* factory kit seo

* factory method seo

* fanout/fanin seo

* feature toggle seo

* filterer seo

* fluent interface seo

* flux seo

* flyweight seo

* front controller seo

* function composition seo

* game loop seo

* gateway seo

* guarded suspension seo

* half-sync/half-async seo

* health check seo

* hexagonal seo

* identity map seo

* intercepting filter seo

* interpreter seo

* iterator seo

* layers seo

* lazy loading seo

* leader election seo

* leader/followers seo

* lockable object seo

* rename and add seo for marker interface

* master-worker seo

* mediator seo

* memento seo

* metadata mapping seo

* microservice aggregator seo

* api gw seo

* microservices log aggregration seo

* mvc seo

* mvi seo

* mvp seo

* mvvm seo

* monad seo

* monitor seo

* monostate seo

* multiton seo

* mute idiom seo

* naked objects & notification seo

* null object seo

* object mother seo

* object pool seo

* observer seo

* optimistic locking seo

* page controller seo

* page object seo

* parameter object seo

* partial response seo

* pipeline seo

* poison pill seo

* presentation model seo

* private class data seo

* producer-consumer seo

* promise seo

* property seo

* prototype seo

* proxy seo

* queue-based load leveling seo

* reactor seo

* registry seo

* repository seo

* RAII seo

* retry seo

* role object seo

* saga seo

* separated interface seo

* serialized entity seo

* serialized lob seo

* servant seo

* server session seo

* service layer seo

* service locator seo

* service to worker seo

* sharding seo

* single table inheritance seo

* singleton seo

* spatial partition seo

* special case seo

* specification seo

* state seo

* step builder seo

* strangler seo

* strategy seo

* subclass sandbox seo

* table module seo

* template method seo

* throttling seo

* tolerant reader seo

* trampoline seo

* transaction script seo

* twin seo

* type object seo

* unit of work seo

* update method seo

* value object seo

* version number seo

* virtual proxy seo

* visitor seo

* seo enhancements

* seo improvements

* SEO enhancements

* SEO improvements

* SEO additions

* SEO improvements

* more SEO improvements

* rename hexagonal + SEO improvements

* SEO improvements

* more SEO stuff

* SEO improvements

* SEO optimizations

* SEO enhancements

* enchance SEO

* improve SEO

* SEO improvements

* update headers
2024-06-08 19:54:44 +03:00

11 KiB

title, shortTitle, description, category, language, tag
title shortTitle description category language tag
Specification Pattern in Java: Enhancing Business Rules with Decoupled Logic Specification Dive deep into the Specification design pattern in Java, a strategic solution for encapsulating business rules. Learn how to implement, combine, and apply this pattern effectively in your software development projects. Behavioral en
Business
Domain
Encapsulation
Enterprise patterns
Extensibility

Also known as

  • Filter
  • Criteria

Intent of Specification Design Pattern

Encapsulate business rules and criteria that an object must satisfy to enable checking these rules in various parts of the application.

Detailed Explanation of Specification Pattern with Real-World Examples

Real-world example

Imagine you are organizing a conference and need to filter attendees based on specific criteria such as registration status, payment completion, and session interests.

Using the Specification design pattern, you would create separate specifications for each criterion (e.g., "IsRegistered", "HasPaid", "IsInterestedInSessionX"). These specifications can be combined dynamically to filter attendees who meet all the required criteria, such as those who are registered, have completed their payment, and are interested in a particular session. This approach allows for flexible and reusable business rules, ensuring that the filtering logic can be easily adjusted as needed without changing the underlying attendee objects.

In plain words

The Specification design pattern in Java enables the efficient encapsulation and reuse of business rules, offering a flexible and dynamic way to combine criteria for robust software development

Wikipedia says

In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic.

Programmatic Example of Specification Pattern in Java

Let's consider a creature pool example. We have a collection of creatures with specific properties. These properties might belong to a predefined, limited set (represented by enums like Size, Movement, and Color) or they might be continuous values (e.g., the mass of a Creature). In cases with continuous values, it's better to use a "parameterized specification," where the property value is provided as an argument when the Creature is instantiated, allowing for greater flexibility. Additionally, predefined and/or parameterized properties can be combined using boolean logic, offering almost limitless selection possibilities (this is known as a "composite specification," explained further below). The advantages and disadvantages of each approach are detailed in the table at the end of this document.

First, here is interface Creature.

public interface Creature {
    String getName();

    Size getSize();

    Movement getMovement();

    Color getColor();

    Mass getMass();
}

And Dragon implementation looks like this.

public class Dragon extends AbstractCreature {

  public Dragon() {
    super("Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0));
  }
}

Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use MovementSelector. The snippet also shows the base class AbstractSelector.

public abstract class AbstractSelector<T> implements Predicate<T> {

    public AbstractSelector<T> and(AbstractSelector<T> other) {
        return new ConjunctionSelector<>(this, other);
    }

    public AbstractSelector<T> or(AbstractSelector<T> other) {
        return new DisjunctionSelector<>(this, other);
    }

    public AbstractSelector<T> not() {
        return new NegationSelector<>(this);
    }
}
public class MovementSelector extends AbstractSelector<Creature> {

  private final Movement movement;

  public MovementSelector(Movement m) {
    this.movement = m;
  }

  @Override
  public boolean test(Creature t) {
    return t.getMovement().equals(movement);
  }
}

On the other hand, when selecting creatures heavier than a chosen amount, we use MassGreaterThanSelector.

public class MassGreaterThanSelector extends AbstractSelector<Creature> {

  private final Mass mass;

  public MassGreaterThanSelector(double mass) {
    this.mass = new Mass(mass);
  }

  @Override
  public boolean test(Creature t) {
    return t.getMass().greaterThan(mass);
  }
}

With these building blocks in place, we can perform some searches.

@Slf4j
public class App {

  public static void main(String[] args) {
    // initialize creatures list
    var creatures = List.of(
        new Goblin(),
        new Octopus(),
        new Dragon(),
        new Shark(),
        new Troll(),
        new KillerBee()
    );
    // so-called "hard-coded" specification
    LOGGER.info("Demonstrating hard-coded specification :");
    // find all walking creatures
    LOGGER.info("Find all walking creatures");
    print(creatures, new MovementSelector(Movement.WALKING));
    // find all dark creatures
    LOGGER.info("Find all dark creatures");
    print(creatures, new ColorSelector(Color.DARK));
    LOGGER.info("\n");
    // so-called "parameterized" specification
    LOGGER.info("Demonstrating parameterized specification :");
    // find all creatures heavier than 500kg
    LOGGER.info("Find all creatures heavier than 600kg");
    print(creatures, new MassGreaterThanSelector(600.0));
    // find all creatures heavier than 500kg
    LOGGER.info("Find all creatures lighter than or weighing exactly 500kg");
    print(creatures, new MassSmallerThanOrEqSelector(500.0));
    LOGGER.info("\n");
    // so-called "composite" specification
    LOGGER.info("Demonstrating composite specification :");
    // find all red and flying creatures
    LOGGER.info("Find all red and flying creatures");
    var redAndFlying = new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING));
    print(creatures, redAndFlying);
    // find all creatures dark or red, non-swimming, and heavier than or equal to 400kg
    LOGGER.info("Find all scary creatures");
    var scaryCreaturesSelector = new ColorSelector(Color.DARK)
        .or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not())
        .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0)));
    print(creatures, scaryCreaturesSelector);
  }

  private static void print(List<? extends Creature> creatures, Predicate<Creature> selector) {
    creatures.stream().filter(selector).map(Objects::toString).forEach(LOGGER::info);
  }
}

Console output:

12:49:24.808 [main] INFO com.iluwatar.specification.app.App -- Demonstrating hard-coded specification :
12:49:24.810 [main] INFO com.iluwatar.specification.app.App -- Find all walking creatures
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg]
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Find all dark creatures
12:49:24.815 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- 

12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating parameterized specification :
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures heavier than 600kg
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures lighter than or weighing exactly 500kg
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Shark [size=normal, movement=swimming, color=light, mass=500.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- KillerBee [size=small, movement=flying, color=light, mass=6.7kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- 

12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating composite specification :
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all red and flying creatures
12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Find all scary creatures
12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]

Adopting the Specification pattern significantly enhances the flexibility and reusability of business rules within Java applications, contributing to more maintainable code.

When to Use the Specification Pattern in Java

Apply the Java Specification pattern when

  • You need to filter objects based on different criteria.
  • The filtering criteria can change dynamically.
  • Ideal for use cases involving complex business rules that must be reused across different parts of an application.

Real-World Applications of Specification Pattern in Java

  • Validating user inputs in enterprise applications.
  • Filtering search results in e-commerce applications.
  • Business rule validation in domain-driven design (DDD).

Benefits and Trade-offs of Specification Pattern

Benefits:

  • Enhances the flexibility and reusability of business rules.
  • Promotes single responsibility principle by separating business rules from the entities.
  • Facilitates unit testing of business rules.

Trade-offs:

  • Can lead to a proliferation of small classes, increasing complexity.
  • Might introduce performance overhead due to the dynamic checking of specifications.
  • Composite: Often used together with Specification to combine multiple specifications.
  • Decorator: Can be used to add additional criteria to a specification dynamically.
  • Strategy: Both patterns involve encapsulating a family of algorithms. Strategy encapsulates different strategies or algorithms, while Specification encapsulates business rules.

References and Credits