본문 바로가기
디자인 패턴

책임 연쇄 패턴 +[F]

by oncerun 2022. 2. 19.
반응형

책임 연쇄 패턴(Chanin of Responsibility Pattern)은 필요한 작업을 여러 객체에 분배하여 객체 간 결합도를 줄이게 해주는 행동 패턴 중의 하나로 명령과 명령을 각각의 방법으로 처리할 수 있는 처리 객체가 존재할 때 처리 객체들을 체인으로 엮고 명령을 처리 객체들이 체인 앞에서부터 하나씩 처리를 시도한다. 

 

각 처리 객체는 자신이 처리할 수 없을 때 체인의 다음 처리 객체로 명령을 넘기며 체인의 끝에 다다르면 처리가 마루리된다.

 

새로운 처리 객체를 추가함으로써 처리 방법을 손쉽게 더할 수 있는 패턴이다.

이 패턴은 요청을 처리할 수 있는 객체가 여러 개이고 처리 객체가 특정적이지 않을 경우 권장됩니다. 

 

다음과 같이 주문을 처리하는 하나의 워크플로우를 정의하기 위해 각 단계마다 자신이 처리할 수 있으면 처리하여 다음 핸들러에게 넘기는 방식으로 마치 Linked List를 구현한 것처럼 사용할 수 있습니다.

 

만약 이해가 잘 안 된다면 스프링 MVC의 구조 중 RequestHandlerAdaptor를 생각해보시면 됩니다.

 스프링의 FrontController패턴 역할을 하는 dispatcherservlet은 해당 요청을 처리할 핸들러를 찾는 과정이 존재합니다.

이 과정에서 반복을 통해 해당 요청을 처리하는 handlerAdaptor의  supports() 메서드를 호출하는데 이 supports()에서 true를 반환하면 다음 워크플로우 과정으로 넘어갑니다.  이후 AgumentResolver 등등 차례대로 워크플로우가 진행됩니다. 

 

이 과정을 책임 연쇄 패턴이라고 할 수 있는지는 잘 모르겠지만 큰 틀에서 생각하면 각 request를 처리하는 과정에서 process가 정상적으로 끝나면 다음 과정으로 넘어가고 Error가 발생한 경우 Error를 처리하는 핸들러가 작동하는 과정과 비슷하다고 생각했습니다.

 

[예시] 하나의 주문이 처리되는 과정

 

주문은 처리상태와 여러 장바구니를 가진다고 가정하겠습니다.

public class Order {

    private OrderStatus status;
    private List<OrderLine> orderLines;
    private BigDecimal amount;

    public enum OrderStatus{
        CREATED,
        IN_PROGRESS,
        ERROR,
        PROCESSED
    }
}

public class OrderLine {

    private long productId;
    private BigDecimal amount;

}

 

이제 처리 객체를 생성합니다.  이 처리 객체는 주문의 상태(OrderStatus)에 따라 자신이 처리할 수 있는 상태인 경우 처리하며 그렇지 않다면 다음 처리 객체로 전달합니다.

 

1) 링크드 리스트 구조 생성

 

다만 단방향으로 시작과 끝이 존재하며 체인의 끝에서는 마무리를 지어야 합니다.

 

public class OrderProcess {

    private OrderProcess next;

    public OrderProcess setNext(OrderProcess nextOrderProcess){
        if (next == null) {
            this.next = nextOrderProcess;
        }else{
            next.setNext(nextOrderProcess);
        }
        return this;
    }

}

 

2. 처리하는 로직을 추가합니다.

public class OrderProcess {

    private final Consumer<Order> handler;
    private OrderProcess next;

    public OrderProcess(Consumer<Order> handler) {
        this.handler = handler;
    }

    public OrderProcess setNext(OrderProcess nextOrderProcess){
        if (next == null) {
            this.next = nextOrderProcess;
        }else{
            next.setNext(nextOrderProcess);
        }
        return this;
    }

    public void handle(Order order) {
        handler.accept(order);
        Optional.ofNullable(next)
                .ifPresent( next -> next.handle(order));
    }

}

Order 객체의 상태 값에 따라 처리 객체가 각 역할을 진행하기 때문에 OrderProcess는 Order상태에 접근하여 로직을 처리해야 합니다.  반환 값은 없다고 가정하고 각 핸들러가 처리할 때  로그를 남기도록 하겠습니다.

 

 

/*
    주문객체가 CREATED 생성된 상태라면 IN_PROGRESS 상태로 변경시켜
    워크플로우를 시작합니다.
 */
OrderProcess initializeOrder = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.CREATED) {
        log.info("Order status initializeOrder CREATED  -> IN_PROGRESS");
        order.setStatus(Order.OrderStatus.IN_PROGRESS);
    }
});
/*
   IN_PROGRESS 상태이면 주문내역을 하나로 모아 주문 총금액을 산출합니다.
 */
OrderProcess sumAmountOrderLine = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.IN_PROGRESS) {
        order.setAmount(order.getOrderLines().stream()
                .map(OrderLine::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        log.info("Order status sumAmountOrderLine IN_PROGRESS 총 주문 금액 : {}", Optional.ofNullable(order.getAmount()).get());
    }
});
/*
   IN_PROGRESS 상태 주문내역을 검증합니다.
 */
OrderProcess verifyOrder = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.IN_PROGRESS) {
        log.info("Order status verifyOrder IN_PROGRESS");
        if(order.getAmount().compareTo(BigDecimal.ZERO) <= 0){
            log.info(" Order Amount Error");
            order.setStatus(Order.OrderStatus.ERROR);
        }
    }
});
/*
    결재진행
 */
OrderProcess paymentOrder = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.IN_PROGRESS) {
        log.info("Order status paymentOrder IN_PROGRESS -> PROCESSED");
        order.setStatus(Order.OrderStatus.PROCESSED);
    }
});

/*
    금액 0원 이하일시 Error
 */
OrderProcess handleOrderError = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.ERROR) {
        log.info("Order status handleOrderError ERROR");
    }
});

/*
    완료시 로그
 */
OrderProcess completeOrder = new OrderProcess( order -> {
    if (order.getStatus() == Order.OrderStatus.PROCESSED) {
        log.info("Order status completeOrder PROCESSED");
    }
});

 

 

이제 Process를 하나로 엮어 chain을 만들고 테스트에 사용할 Order객체도 같이 준비합니다.

Order order = new Order();
order.setOrderLines(Arrays.asList(new OrderLine(1L, BigDecimal.valueOf(2000)),
                                 new OrderLine(2L, BigDecimal.valueOf(3000))));
order.setStatus(Order.OrderStatus.CREATED);

OrderProcess chainOrderProcesses = initializeOrder
        .setNext(sumAmountOrderLine)
        .setNext(verifyOrder)
        .setNext(paymentOrder)
        .setNext(handleOrderError)
        .setNext(completeOrder);

chainOrderProcesses.handle(order);

 

Error인 경우 Error 핸들러가 처리 후 completeOrder 프로세스를 호출하지만  completeOrder 프로세스는 자신이 처리할 수 없기 때문에 처리하지 않고 다음을 호출하지만 다음이 없기 때문에 마무리가 됩니다.

Order order = new Order();
order.setOrderLines(Arrays.asList(new OrderLine(1L, BigDecimal.valueOf(-1000)),
                                 new OrderLine(2L, BigDecimal.valueOf(-2000))));
order.setStatus(Order.OrderStatus.CREATED);

OrderProcess chainOrderProcesses = initializeOrder
        .setNext(sumAmountOrderLine)
        .setNext(verifyOrder)
        .setNext(paymentOrder)
        .setNext(handleOrderError)
        .setNext(completeOrder);

chainOrderProcesses.handle(order);

 

 

 

반응형

'디자인 패턴' 카테고리의 다른 글

GoF 행동 패턴 소개 (1)  (0) 2022.11.15
GoF 구조적인 패턴 소개  (0) 2022.11.06
데코레이터 패턴 + [F]  (0) 2022.02.19
빌더 패턴 + [F]  (0) 2022.02.19
데코레이터 패턴  (0) 2022.01.21

댓글