DesignPattern 적용하기 #1 책임연쇄패턴

강철진·2023년 3월 7일
0

DesignPattern

목록 보기
2/5
post-thumbnail

배경

고객사는 우리의 서비스를 통해 고객에게 쿠폰을 발행할 수 있습니다. 모든 고객에게 발행하는 것이 아닌 특정 타겟들에게 쿠폰을 발행할 수 있습니다. 그럼 저희는 해당 고객이 고객사에서 설정한 캠페인 옵션에 따라 해당 쿠폰을 받을 조건이 되는지 검증이 필요합니다.

기존 코드의 문제점

@Service
public class ServiceImpl implements Service {
	...
    public boolean getValidateResult(ValidateObject obj) {
    	... 비즈니스 로직
  		if(isUseValidate1) {
      		validate1();
        } 
        if(isUseValidate2) {
            // 검증로직
        }
        if(isUseValidate3) {
            // 검증로직
        }
        ... 비즈니스 로직
        ... 검증 로직
        
    }
    ...
    private boolean validate1() {
    	...
        검증로직
        ...
    }
}
...

간략히 형태만 표현한 것이니 크게 복잡해보이지 않으나 실제로는 코드량이 많고 같은 클래스 안에 private method 를 호출해서 사용하는 부분도 존재합니다. 또한 비즈니스로직을 통해 나온 결과 값들의 검증이 이루어지다보니 중요한 비즈니스 로직을 한눈에 보기 어려웠습니다. 이를 해결하고자 책임연쇄패턴을 활용하게되었습니다.

책임연쇄패턴이란?

간단히 소개하자면 책임 연쇄 패턴(Chain of Responsibility)이란 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다. 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지를 결정합니다.

Class Diagram

전체적인 코드흐름은 다음과 같습니다.

  1. AbstractValidateHandler 클래스는 추상클래스로 각 검증 단계(Validate1Handler ...)들은 해당 클래스를 상속받습니다.

  2. 실제 Validate 구현체(각 검증단계 클래스)들은 isValidateTrue 함수를 통해 로직 수행.

  3. 실제 검증을 진행할 클래스에서 setNextValidate 함수를 통해 각 검증들을 체이닝 방식으로 각 검증단계를 연결하여 객체를 생성합니다.

  4. 완성된 객체의 validationExecute 함수를 실행하여 각 검증단계를 차례로 실행합니다.

책임연쇄패턴 적용 코드

ValidateHandler.java

public interface ValidateHandler {
    boolean validationExecute(Param1 param1);
}

AbstractValidateHandler.java

public abstract class AbstractValidateHandler implements ValidateHandler {
    private AbstractValidateHandler nextValidateHandler;

    @Override
    public boolean validationExecute(Param1 param1) {
        if(isValidateTrue(param1)) {
            if(nextValidateHandler != null)
                return nextValidateHandler.validationExecute(param1);
            else
                return true;
        }
        return false;
    }

    public AbstractValidateHandler setNextValidate(AbstractValidateHandler nextHandler) {
        this.nextValidateHandler = nextHandler;
        return nextHandler;
    }

    protected abstract boolean isValidateTrue(Param1 param1);
}

Validate1Handler.java

public class Validate1Handler extends AbstractValidateHandler {
    @Override
    protected boolean isValidateTrue(Param1 param1) {
        // 검증로직
        boolean result = true;
        return result;
    }
}

ServiceImpl.java

@Service
public class ServiceImpl implements Service {
	...
    public getParamValidateResult(Param1 param1) {
    	AbstractValidateHandler validate1 = new Validate1Handler();
        AbstractValidateHandler validate2 = new Validate2Handler();
        AbstractValidateHandler validate1 = new Validate3Handler();
        
        // 체이닝 방식으로 순서에 맞게 validation 진행
        AbstractValidateHandler vlidateProcess = validate1
        .setNextValidate(validate2)
        .setNextValidate(validate3);
        
        return vlidateProcess.validationExecute(param1);
    }
    ...
}

ServiceImpl 의 코드를 보면 어떠한 순서로 어떠한 것들을 검증하고 있는지 한 눈에 알아보기 쉬워졌습니다. 내부 로직은 필요할 때 그 클래스 내부(isValidateTrue 메서드)를 보면 되고 수정이 필요할 때는 해당 클래스의 검증 메서드만 수정하면 되기때문에 유지보수에도 훨씬 용이합니다.

기존 검증로직에 추가로 또다른 검증로직이 생긴다면 개발자는 AbstractValidateHandler 를 상속받은 새로운 검증 핸들러를 구현하여 체이닝에 연결만 해주면 됩니다.

썸네일 이미지 및 학습자료 출처
https://refactoring.guru/ko/design-patterns/chain-of-responsibility

profile
자바/스프링 백엔드 개발자입니다.

0개의 댓글