책임 연쇄 패턴
요청을 처리할 수 있는 여러 객체들 사이에서 요청을 전달하는 디자인 패턴. 이 패턴을 사용하면 요청을 보내는 쪽과 이를 처리하는 쪽을 분리할 수 있으며, 요청을 처리할 객체가 누구인지 클라이언트는 몰라도 된다.
구조
- Handler: 모든 처리기(Handler)에 공통적인 인터페이스를 정의한다. 이 인터페이스는 일반적으로 요청을 처리하는 메소드를 포함한다.
- ConcreteHandler: 실제 요청을 처리하는 클래스. 만약 해당 클래스가 요청을 처리할 수 없다면 다음 처리기로 요청을 전달한다.
- Client: 요청을 처리기 체인에 보내는 역할. 클라이언트는 체인의 어떤 부분이 요청을 처리하는지 알 필요가 없다.
기본 인터페이스
// Handler 인터페이스
public interface IHandler
{
void SetNext(IHandler handler);
void HandleRequest(string request);
}
// ConcreteHandler 클래스
public class ConcreteHandler1 : IHandler
{
private IHandler _nextHandler;
public void SetNext(IHandler handler)
{
_nextHandler = handler;
}
public void HandleRequest(string request)
{
if (CanHandle(request))
{
Console.WriteLine($"Handled by ConcreteHandler1");
}
else
{
_nextHandler?.HandleRequest(request);
}
}
private bool CanHandle(string request)
{
// 요청을 처리할 수 있는지 여부를 결정하는 로직
}
}
// 클라이언트 클래스
public class Client
{
public void Main()
{
IHandler handler1 = new ConcreteHandler1();
IHandler handler2 = new ConcreteHandler2();
handler1.SetNext(handler2);
handler1.HandleRequest("request");
}
}
왜 사용해야 할까?
책임 연쇄 패턴은 여러 객체에 걸쳐 요청 처리의 책임을 분산시키는 디자인 패턴으로, 이를 통해 각 객체는 요청을 처리하거나 다음 객체로 전달할 수 있는 유연성을 갖는다. 이 패턴의 주요 사용 이유는 각 객체가 자신의 책임을 갖고, 처리할 수 없는 요청을 다음 객체에게 넘길 수 있기 때문에, 복잡한 조건부 로직이나 if-else 체인을 피할 수 있다는 것이다. 이러한 접근 방식은 코드의 재사용성을 높이고, 유지보수를 용이하게 하며, 시스템의 확장성을 향상시킨다.
이를 통해 요청 처리 객체들 간의 결합도가 낮아지고, 각 객체는 단일 책임 원칙에 따라 설계될 수 있다. 또한, 요청 처리 체인에 새로운 객체를 추가하는 것이 간단해지므로, 시스템의 확장성이 상승한다.
한 예시로, 사용자 지원 시스템에서 다양한 유형의 문의를 처리하는 경우를 생각해볼 수 있다. 각 문의는 먼저 기초적인 문제 해결 담당자에게 전달되고, 그들이 처리할 수 없는 문제는 더 고급 기술 지원 팀으로 이동한다.
// 처리자 인터페이스
public interface ISupportHandler
{
ISupportHandler SetNext(ISupportHandler handler);
string HandleSupport(string issueType);
}
// 기초 문제 해결 처리자
public class BasicSupportHandler : ISupportHandler
{
private ISupportHandler _nextHandler;
public ISupportHandler SetNext(ISupportHandler handler)
{
_nextHandler = handler;
return handler;
}
public string HandleSupport(string issueType)
{
if (issueType == "Basic")
{
return "BasicSupportHandler: 문제 해결";
}
else
{
return _nextHandler?.HandleSupport(issueType);
}
}
}
// 고급 기술 지원 처리자
public class AdvancedSupportHandler : ISupportHandler
{
private ISupportHandler _nextHandler;
public ISupportHandler SetNext(ISupportHandler handler)
{
_nextHandler = handler;
return handler;
}
public string HandleSupport(string issueType)
{
if (issueType == "Advanced")
{
return "AdvancedSupportHandler: 고급 문제 해결";
}
else
{
return _nextHandler?.HandleSupport(issueType);
}
}
}
BasicSupportHandler와 AdvancedSupportHandler는 서로 다른 유형의 지원 문제를 처리한다. 문제 유형에 따라 적절한 처리자가 요청을 처리하며, 처리할 수 없는 문제는 다음 처리자에게 전달된다. 클라이언트는 처리자 체인의 첫 번째 요소에 요청을 보내고, 요청은 체인을 통해 적절한 처리자에 도달하게 된다.
// 클라이언트 코드
class Client
{
public static void Main(string[] args)
{
var basicSupport = new BasicSupportHandler();
var advancedSupport = new AdvancedSupportHandler();
basicSupport.SetNext(advancedSupport);
// 기초적인 문제 요청
Console.WriteLine(basicSupport.HandleSupport("Basic"));
// 고급 기술 지원 필요한 문제 요청
Console.WriteLine(basicSupport.HandleSupport("Advanced"));
}
}
그러나 체인이 길어지면 성능 문제가 발생할 수 있으며, 요청 처리 과정이 복잡해질 수 있다. 따라서 적절한 상황에서 적절하게 사용되어야 한다.
장점
- 클라이언트는 요청을 처리하는 구체적인 클래스를 몰라도 된다. 이는 클라이언트와 서버 사이의 결합도를 감소시킨다.
- 처리기를 재사용하거나 조합하여 다양한 방식으로 요청을 처리할 수 있다.
단점
- 요청이 체인의 많은 핸들러를 거치면서 지연될 수 있다.
- 어떤 핸들러가 요청을 처리했는지 추적하기 어려울 수 있으며, 이는 디버깅을 어렵게 만든다.
전체 예시 코드
참고자료
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
15. [Design Pattern] 인터프리터(interpreter) 패턴 (0) | 2024.01.14 |
---|---|
14. [Design Pattern] 커맨드(Command) 패턴 (0) | 2024.01.09 |
12. [Design Pattern] 프록시(Proxy) 패턴 (1) | 2024.01.06 |
11. [Design Pattern] 플라이웨이트(Flyweight) 패턴 (0) | 2023.12.27 |
10. [Design Pattern] 퍼사드(Facade) 패턴 (0) | 2023.12.25 |