![책임 연쇄](/images/patterns/cards/chain-of-responsibility-mini.png?id=36d85eba8d14986f053123de17aac7a7)
루비로 작성된 책임 연쇄
책임 연쇄 패턴은 핸들러 중 하나가 요청을 처리할 때까지 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다.
이 패턴은 발신자 클래스를 수신자들의 구상 클래스들에 연결하지 않고도 여러 객체가 요청을 처리할 수 있도록 합니다. 체인은 표준 핸들러 인터페이스를 따르는 모든 핸들러와 런타임 때 동적으로 구성될 수 있습니다.
복잡도:
인기도:
사용 예시들: 책임 연쇄 패턴은 루비 코드에 매우 일반적이며, 당신의 코드가 필터, 이벤터 체인 등과 같은 객체 체인과 함께 작동할 때 특히 유용합니다.
식별: 패턴의 모든 객체는 공통 인터페이스를 따르며, 다른 객체들의 같은 메서드들을 간접적으로 호출하는 한 객체 그룹의 행동 메서드들이 있습니다.
개념적인 예시
이 예시는 책임 연쇄 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
main.rb: 개념적인 예시
# The Handler interface declares a method for building the chain of handlers. It
# also declares a method for executing a request.
class Handler
# @abstract
#
# @param [Handler] handler
def next_handler=(handler)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# @abstract
#
# @param [String] request
#
# @return [String, nil]
def handle(request)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# The default chaining behavior can be implemented inside a base handler class.
class AbstractHandler < Handler
# @return [Handler]
attr_writer :next_handler
# @param [Handler] handler
#
# @return [Handler]
def next_handler(handler)
@next_handler = handler
# Returning a handler from here will let us link handlers in a convenient
# way like this:
# monkey.next_handler(squirrel).next_handler(dog)
handler
end
# @abstract
#
# @param [String] request
#
# @return [String, nil]
def handle(request)
return @next_handler.handle(request) if @next_handler
nil
end
end
# All Concrete Handlers either handle a request or pass it to the next handler
# in the chain.
class MonkeyHandler < AbstractHandler
# @param [String] request
#
# @return [String, nil]
def handle(request)
if request == 'Banana'
"Monkey: I'll eat the #{request}"
else
super(request)
end
end
end
class SquirrelHandler < AbstractHandler
# @param [String] request
#
# @return [String, nil]
def handle(request)
if request == 'Nut'
"Squirrel: I'll eat the #{request}"
else
super(request)
end
end
end
class DogHandler < AbstractHandler
# @param [String] request
#
# @return [String, nil]
def handle(request)
if request == 'MeatBall'
"Dog: I'll eat the #{request}"
else
super(request)
end
end
end
# The client code is usually suited to work with a single handler. In most
# cases, it is not even aware that the handler is part of a chain.
def client_code(handler)
['Nut', 'Banana', 'Cup of coffee'].each do |food|
puts "\nClient: Who wants a #{food}?"
result = handler.handle(food)
if result
print " #{result}"
else
print " #{food} was left untouched."
end
end
end
monkey = MonkeyHandler.new
squirrel = SquirrelHandler.new
dog = DogHandler.new
monkey.next_handler(squirrel).next_handler(dog)
# The client should be able to send a request to any handler, not just the first
# one in the chain.
puts 'Chain: Monkey > Squirrel > Dog'
client_code(monkey)
puts "\n\n"
puts 'Subchain: Squirrel > Dog'
client_code(squirrel)
output.txt: 실행 결과
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Monkey: I'll eat the Banana
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.