본문 바로가기
DesignPattern

[Design Pattern] Chain of Responsibility Pattern

by 봄석 2019. 10. 21.

[Design Pattern] Chain of Responsibility Pattern

Chain of Responsibility Pattern 패턴에 대하여 알아보고, 자바와 코틀린 예제를 알아보도록 하겠습니다.

 

 

 

Chain of Responsibility Pattern이란????

 요청 처리가 들어오게 되면 그것을 수신하는 객체가 자신이 처리할 수 없는 경우에는

다음 객체에게 문제를 넘김으로써 최종적으로 요청을 처리할 수 있는 객체의 의해 처리가 가능하도록 하는 패턴입니다.

 

하나의 예로 자바의  try /catch / finally가 해당됩니다.

try 블록 안에서 Exception이 발생하면 catch 블록으로  이동하여 catch블록을 실행하게 되는 것입니다.

 

 

구조

● Handler : 요청을 처리하기 위한 수신자들이 가져야 할 인터페이스를 정의
● ConcreteHandler : Handler 인터페이스 구현, 각자가 요청 종류에 따라 자신이 처리할 수 있는 부분을 구현
● Client : 맨 처음 수신자에게 처리를 요구

 

 

 

Chain of Responsibility Pattern with Java

abstract class PurchasePower {
    protected final double base = 500;
    protected PurchasePower successor;

    public void setSuccessor(PurchasePower successor) {
        this.successor = successor;
    }

    abstract public void processRequest(PurchaseRequest request);
}
class ManagerPower extends PurchasePower {
    private final double ALLOWABLE = 10 * base;

    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Manager will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
class DirectorPower extends PurchasePower {
    private final double ALLOWABLE = 20 * base;

    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Director will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
class VicePresidentPower extends PurchasePower {
    private final double ALLOWABLE = 40 * base;

    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Vice President will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
class PresidentPower extends PurchasePower {
    private final double ALLOWABLE = 60 * base;

    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("President will approve $" + request.getAmount());
        } else {
            System.out.println( "Your request for $" + request.getAmount() + " needs a board meeting!");
        }
    }
}

 

 

class PurchaseRequest {
    private int number;
    private double amount;
    private String purpose;

    public PurchaseRequest(int number, double amount, String purpose) {
        this.number = number;
        this.amount = amount;
        this.purpose = purpose;
    }

    public double getAmount() {
        return amount;
    }
    public void setAmount(double amt)  {
        amount = amt;
    }

    public String getPurpose() {
        return purpose;
    }
    public void setPurpose(String reason) {
        purpose = reason;
    }

    public int getNumber(){
        return number;
    }
    public void setNumber(int num) {
        number = num;
    }
}
class CheckAuthority {
    public static void main(String[] args) {
        ManagerPower manager = new ManagerPower();
        DirectorPower director = new DirectorPower();
        VicePresidentPower vp = new VicePresidentPower();
        PresidentPower president = new PresidentPower();
        manager.setSuccessor(director);
        director.setSuccessor(vp);
        vp.setSuccessor(president);

        // Press Ctrl+C to end.
        try {
            while (true) {
                System.out.println("Enter the amount to check who should approve your expenditure.");
                System.out.print(">");
                double d = Double.parseDouble(new BufferedReader(new InputStreamReader(System.in)).readLine());
                manager.processRequest(new PurchaseRequest(0, d, "General"));
           }
        } catch(Exception e) {
            System.exit(1);
        }
    }
}

Authority를 체크하는 프로그램입니다.

들어온 Amount에  따라 

Manager->Director -> VicePresident->President 순으로 Responsibliity를 check 합니다.

Check한후 자신의 ALLOWABLE보다  크다면 다음 PurchasePower으로 책임을 넘깁니다.

 

 

 

Chain of Responsibility Pattern with Kotlin

ex1) header interperter

interface HeadersChain {
    fun addHeader(inputHeader: String): String
}
class AuthenticationHeader(val token: String?, var next: HeadersChain? = null) : HeadersChain {

    override fun addHeader(inputHeader: String): String {
        token ?: throw IllegalStateException("Token should be not null")
        return inputHeader + "Authorization: Bearer $token\n"
            .let { next?.addHeader(it) ?: it }
    }
}
class ContentTypeHeader(val contentType: String, var next: HeadersChain? = null) : HeadersChain {

    override fun addHeader(inputHeader: String): String =
        inputHeader + "ContentType: $contentType\n"
            .let { next?.addHeader(it) ?: it }
}
class BodyPayload(val body: String, var next: HeadersChain? = null) : HeadersChain {

    override fun addHeader(inputHeader: String): String =
        inputHeader + "$body"
            .let { next?.addHeader(it) ?: it }
}

헤더의 정보 authenticationHeader->contentTypeHeader->messageBody순으로 체크하며

해당 정보가 존재하지 않는다면 책임을 넘깁니다.

 

ex2) Cash

data class CashRequest(val amount: Int, val notes: List<Pair<Int, Int>> = listOf())
fun buildCashRequestHandlerForNote(note: Int): CashRequestHandler = { cashRequest ->
    if (cashRequest.amount > note) CashRequest(
            cashRequest.amount % note,
            cashRequest.notes + Pair(note, cashRequest.amount / note)
    )
    else cashRequest
}


val cashRequestHandlerChain: CashRequestHandler = listOf(100, 50, 20, 10, 5)
    .map(::buildCashRequestHandlerForNote)
    .reduce { chain, handler -> { cashRequest -> handler(chain(cashRequest)) } }

cashRequest.amount가 note보다 큰지 비교합니다.

크지 않다면 다음 request를  다음으로 넘깁니다.

val cashRequestHandlerChain에서 

reduce를  통하여 책임을 모두 확인합니다.

 

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ChainOfResponsibilityTest {

    @DisplayName("Given a cash request handler chain. When handle the amount. Then return a set of notes equivalent to the amount")
    @ParameterizedTest(name = "{0} = {1}")
    @MethodSource("amountProvider")
    fun givenCashRequestHandlerChain_whenHandleAmount_thenReturnSetOfNotesEquivalentToTheAmount(
        cashRequest: CashRequest,
        expectedResponse: CashRequest
    ) {
        // Given & When
        val response = cashRequestHandlerChain(cashRequest)

        // Then
        assertThat(response,CoreMatchers.equalTo(expectedResponse))
    }

    fun amountProvider(): Stream<Arguments> = Stream.of(
            Arguments.of(CashRequest(4), CashRequest(4, listOf<Pair<Int, Int>>())),
            Arguments.of(CashRequest(14), CashRequest(4, listOf(Pair(10, 1)))),
            Arguments.of(CashRequest(134), CashRequest(4, listOf(Pair(100, 1), Pair(20, 1), Pair(10, 1)))),
            Arguments.of(CashRequest(484), CashRequest(4, listOf(Pair(100, 4), Pair(50, 1), Pair(20, 1), Pair(10, 1))))
    )
}

 

 

 

정리

책임 연쇄 패턴(Chain of Responsibility) 패턴에서

각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들 할 수 없는 명령은 다음 처리 객체로 넘겨집니다.

이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복됩니다.

 

 

샘플 코드 보러 가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

'DesignPattern' 카테고리의 다른 글

[Design Pattern] Visitor Pattern  (0) 2019.10.21
[Design Pattern] State Pattern  (0) 2019.10.21
[Design Pattern] Command Pattern  (0) 2019.10.21
[Design Pattern] Interpreter Pattern  (0) 2019.10.21
[Design Pattern] Iterator Pattern  (0) 2019.10.18

댓글