Chain of Responsibility

DongHeon·2022년 12월 20일
0

디자인 패턴

목록 보기
12/12

오늘은 Chain of Responsibility(책임 연쇄) 패턴에 대해 알아보겠습니다.

객체는 하나의 책임을 가지고 있고 그러한 책임들을 연결하는 패턴입니다.

책임?
"객체 지향의 사실과 오해" 책에서는 책임을 외부에 제공해 줄 수 있는 정보 및 서비스의 목록이라고 설명하고 있습니다.

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 타입을 알아야 합니다.

패턴 적용

  • Handler
protocol Handler {
    var nextHandler: Handler? { get }
    
    func doSomething(request: Request)
}
  • ConcreteHandler
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)
        }
    }
}
  • Client
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 디자인 패턴 강의를 참고해 작성했습니다.

⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️

0개의 댓글