책임 연쇄 패턴(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 |
댓글