Chain of Responsibility 패턴

김태현·2022년 8월 28일
0

CS

목록 보기
5/7

정의

  • 행위 패턴 중 하나
  • command objects + processing objects
  • processing object = 다룰 수 있는 command object type을 정의
  • 다룰 수 없는 것들은 chain의 다른 processing objects에 전달
  • chain 마지막에 새 processing object 추가 가능
  • 구조적으로 decorated pattern 과 유사
    • decorated pattern: 모든 클래스가 요청 처리
    • chain of responsibility: 한 클래스가 요청 처리

iOS의 예시: UIResponder

The Cocoa and Cocoa Touch frameworks, used for OS X and iOS applications respectively, actively use the chain-of-responsibility pattern for handling events. . . . inheriting from UIResponder . . .

UIResponder
An abstract interface for responding to and handling events.
If a given responder does not handle an event, it forwards that event to the next event in the responder chain.

장점

  • loose coupling
  • 요청의 발신자와 수신자를 분리
  • chain 처리 순서를 변경/ 추가/ 삭제가 유연

단점

  • 처리 객체들 내부에서 사이클 발생 가능
  • 디버깅 및 테스트가 힘듦
  • 요청을 처리할 객체가 chain 내부에서 어느 위치에 있을지는 동적으로 변경됨 -> 시간 예측이 어려움

UML

예제 코드

enum LogLevel: UInt8 {
    case Zero = 0b0000_0000
    case One = 0b0000_0001
    case Two = 0b0000_0010
    case Three = 0b0000_0100
    case Four = 0b0000_1000
    case Five = 0b0001_0000
    case Six = 0b0010_0000
    case All = 0b1111_1111
}

class Logger {
    var logLevel: UInt8
    var next: Logger?
    
    init(logLevel: LogLevel) {
        self.logLevel = logLevel.rawValue
    }
    
    init(logLevel: UInt8) {
        self.logLevel = logLevel
    }
    
    func setNext(nextlogger: Logger) {
        var lastLogger = self
        
        while lastLogger.next != nil {
            lastLogger = lastLogger.next!
        }
        
        lastLogger.next = nextlogger
    }
    
    func message(message: String, severity: LogLevel) {
        if (severity.rawValue & logLevel) != 0 { 
            // 두 값을 비교했을 때 같은 자리에 하나라도 1이 있으면
            writeMessage(message)
        }
        
        next?.message(message: message, severity: severity)
    }
    
    func writeMessage(_ message: String) { }
}

class ConsoleLogger: Logger {
    override func writeMessage(_ message: String) {
        print("console: " + message);
    }
}

class EmailLogger : Logger {
    override func writeMessage(_ message: String) {
        print("email: " + message);
    }
}

class FileLogger: Logger {
    override func writeMessage(_ message: String) {
        print("Log File: " + message);
    }
}

final class Program {
    static func Main() {
        let logger: Logger
        
        logger = ConsoleLogger(logLevel: .Zero)
        
        let emailLevel = LogLevel.Five.rawValue | LogLevel.Six.rawValue
        logger.setNext(nextlogger: EmailLogger(logLevel: emailLevel))
        
        let fileLevel = LogLevel.Three.rawValue | LogLevel.Four.rawValue
        logger.setNext(nextlogger: FileLogger(logLevel: fileLevel))
        
        logger.message(message: "AAA", severity: LogLevel.Two)
        logger.message(message: "BBB", severity: LogLevel.One)
        
        logger.message(message: "CCC", severity: LogLevel.Three)
        logger.message(message: "DDD", severity: LogLevel.Four)
        
        logger.message(message: "EEE", severity:  LogLevel.Six)
        logger.message(message: "FFF", severity: LogLevel.Five)
    }
}

Program.Main()

/*
 logger = ConsoleLogger(logLevel: .All) 인 경우:
 
 console: AAA
 console: BBB
 console: CCC
 Log File: CCC
 console: DDD
 Log File: DDD
 console: EEE
 email: EEE
 console: FFF
 email: FFF
 */

/*
 logger = ConsoleLogger(logLevel: .Zero) 인 경우:
 
 Log File: CCC
 Log File: DDD
 email: EEE
 email: FFF
 */

참고 자료 1: 정의, iOS의 예시 _위키 & 예제 코드
참고 자료 2: iOS의 예시 _UIResponder
참고 자료 2: iOS의 예시 _그림
참고 자료 3: 장단점
참고 자료 4: bit 연산자

profile
iOS 공부 중

0개의 댓글