[Design Pattern] Specification Pattern

March 29, 2022 - 4 minute read -
design-pattern visitor-pattern

Specification pattern 은 복잡한 비즈니스 규칙이 있는 불리언 로직을 간단하게 표현할 수 있는 패턴이다.

Specification pattern 을 이용하면 논리 연산하는 복잡한 로직을 객체지향적으로 표현할 수 있으며,
하나의 객체로 두고 재활용할 수 있다는 것이 장점이다.

논리연산이 중복되거나 그 의미를 명확하게 표현하고 싶다면 유용하게 사용할 수 있는 패턴이다.

구조

Specification pattern structure
Specification pattern structure

구현하는 방식에 따라 조금씩 차이가 있을 수 있지만 나는 위와 같이 정의했다.
인터페이스에서 마지막에 참, 거짓을 판단하는 isSatisfiedBy 메소드와 논리연산을 하는 and, or, not 을 정의한다.

추상 클래스에서는 논리 연산 메소드드들에서는 각각 알맞는 구현체들을 반환해주고
구현체들은 본인의 역할에 맞는 isSatisfiedBy 만 구현하면 된다.

구현

나의 경우에는 인터페이스를 정의하면서 타입 안전하게 사용할 수 있도록 제너릭을 추가했다.
이렇게 제너릭을 이용하면 내부에서 캐스팅없이 원하는 객체의 타입을 다룰 수 있다.

또한, 더 유연하게 사용하기 위해 상위 타입의 Specification 도 수용할 수 있도록 super 키워드도 추가했다.

Specification

인터페이스에서 and, or, not 이외에도 필요한 논리 연산이 있다면 메소드를 추가하도록 한다.


interface Specification<T> {

    Specification<T> and(Specification<? super T> other);
    Specification<T> andNot(Specification<? super T> other);
    Specification<T> or(Specification<? super T> other);
    Specification<T> orNot(Specification<? super T> other);
    Specification<T> not();
    boolean isSatisfiedBy(T candidate);
}

CompositeSpecification (Abstract Class)


public abstract class CompositeSpecification<T> implements Specification<T> {
    @Override
    public Specification<T> and(Specification<? super T> other) {
        return new AndSpecification<>(this, other);
    }
    @Override
    public Specification<T> andNot(Specification<? super T> other) {
        return new AndNotSpecification<>(this, other);
    }
    @Override
    public Specification<T> or(Specification<? super T> other) {
        return new OrSpecification<>(this, other);
    }
    @Override
    public Specification<T> orNot(Specification<? super T> other) {
        return new OrNotSpecification<>(this, other);
    }
    @Override
    public Specification<T> not() {
        return new NotSpecification<>(this);
    }
}

implementation


public class AndSpecification<T> extends CompositeSpecification<T> {

    private final Specification<? super T> leftCondition;
    private final Specification<? super T> rightCondition;

    public AndSpecification(Specification<? super T> leftCondition, Specification<? super T> rightCondition) {
        this.leftCondition = leftCondition;
        this.rightCondition = rightCondition;
    }
    @Override
    public boolean isSatisfiedBy(T candidate) {
        return leftCondition.isSatisfiedBy(candidate) && rightCondition.isSatisfiedBy(candidate);
    }
}

public class OrNotSpecification<T> extends CompositeSpecification<T> {

    private final Specification<? super T> leftCondition;
    private final Specification<? super T> rightCondition;

    public OrNotSpecification(Specification<? super T> leftCondition, Specification<? super T> rightCondition) {
        this.leftCondition = leftCondition;
        this.rightCondition = rightCondition;
    }
    @Override
    public boolean isSatisfiedBy(T candidate) {
        return leftCondition.isSatisfiedBy(candidate) || !rightCondition.isSatisfiedBy(candidate);
    }
}
public class NotSpecification<T> extends CompositeSpecification<T> {

    private final Specification<? super T> condition;

    public NotSpecification(Specification<? super T> condition) {
        this.condition = condition;
    }
    @Override
    public boolean isSatisfiedBy(T candidate) {
        return !condition.isSatisfiedBy(candidate);
    }
}

전체 코드는 깃허브 레포지토리 참고

Review

Specification 패턴은 복잡한 논리연산을 쉽게 정리하고 재활용할 수 있는 유용한 패턴이다.
여기에서는 코드를 직접 구현했지만 java 에서는 Predicate<T>이 이 패턴을 이용했기 때문에 직접 구현할 필요는 없다.
그래서 Predicate<T>를 많이 이용하는 Stream API 에서도 이 패턴을 유용하게 사용할 수 있다.

출처