오늘은 Chain of Responsibility(책임 연쇄) 패턴에 대해 알아보겠습니다.
객체는 하나의 책임을 가지고 있고 그러한 책임들을 연결하는 패턴입니다.
책임?
"객체 지향의 사실과 오해" 책에서는 책임을 외부에 제공해 줄 수 있는 정보 및 서비스의 목록이라고 설명하고 있습니다.
요청을 보내는 쪽과 처리하는 쪽을 분리하는 패턴입니다.
Client는 구체적인 Handler 타입을 몰라도 사용가능합니다.
class Request {
private let request: String
init(request: String) {
self.request = request
}
func getRequest() -> String {
return self.request
}
}
class RequestHandler {
func doRequest(request: Request) {
print("\(request.getRequest()) 시작")
}
}
class Client {
func doWork() {
let request = Request(request: "요청")
let handler = RequestHandler()
handler.doRequest(request: request)
}
}
요청을 처리하는 코드가 존재합니다. 만약 여기서 인증 기능을 추가하고 싶다면 어떻게 할까요?
기능 추가를 하기 위해 선택할 수 있는 방법 중 하나는 RequestHandler의 코드를 수정하거나 RequestHandler를 상속받는 하위 클래스를 생성하는 것입니다.
RequestHandler를 직접 수정하게 되면 OCP를 위배하게 되고 만약 RequestHandler의 코드를 수정할 수 없는 경우도 존재할 수 있기 때문에 상속을 사용해 보겠습니다.
class AuthRequestHandler: RequestHandler {
override func doRequest(request: Request) {
print("인증 작업을 시작합니다.")
super.doRequest(request: request)
}
}
class Client {
func doWork() {
let request = Request(request: "요청")
let handler = AuthRequestHandler()
handler.doRequest(request: request)
}
}
RequestHandler를 상속받는 AuthRequestHandler를 구현해 재정의를 통해 기능을 확장할 수 있습니다.
하지만 Client입장에서는 구체적인 Handler 타입을 알아야 합니다.
protocol Handler {
var nextHandler: Handler? { get }
func doSomething(request: Request)
}
class RequestHandler: Handler {
var nextHandler: Handler?
init(nextHandler: Handler? = nil) {
self.nextHandler = nextHandler
}
func doSomething(request: Request) {
print("\(request.getRequest()) 시작")
if nextHandler == nil {
return
} else {
nextHandler?.doSomething(request: request)
}
}
}
class AuthRequestHandler: Handler {
var nextHandler: Handler?
init(nextHandler: Handler? = nil) {
self.nextHandler = nextHandler
}
func doSomething(request: Request) {
print("인증 작업을 시작합니다.")
if nextHandler == nil {
return
} else {
nextHandler?.doSomething(request: request)
}
}
}
class Client {
private let handler: Handler
init(handler: Handler) {
self.handler = handler
}
func doWork() {
let request = Request(request: "요청")
handler.doSomething(request: request)
}
}
let chain = AuthRequestHandler(nextHandler: RequestHandler())
let client = Client(handler: chain)
client.doWork()
Client의 코드를 변경하지 않고 새로운 기능을 추가할 수 있습니다. (추상화된 Handler 타입을 사용하기 때문)
필요한 기능을 조합해서 사용하기 편리합니다. 기존에는 새로운 기능을 추가하기 위해서는 계속해서 새로운 객체를 구현해야 하지만 해당 패턴을 사용하면 기존의 기능들을 조합해 사용할 수 있습니다.
장점
1. Client 코드를 변경하지 않고 새로운 기능 추가
2. 하나의 객체가 하나의 역할을 한다.
단점
1. 연쇄적으로 묶여있기 때문에 디버깅이 힘들다.
(어느 곳에서 오류가 발생했는지 찾기 힘들다.)
디버깅?
디버그(Debug)는 프로그래밍 과정중에 발생하는 오류나 비정상적인 연산, 즉 버그를 찾고 수정하는 것이다. 이 과정을 디버깅(Debugging)이라 하기도 한다.
출처 : 나무위키
해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의를 참고해 작성했습니다.
⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️