[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
'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 |
댓글