의도
- 메시지를 보내는 개체와 이를 받아 처리하는 객체들 간의 결합도를 없애기 위한 패턴. 하나의 요청에 대한 처리가 반드시 한 객체에서만 되지 않고, 여러 객체에게 그 처리 기회를 주려는 것
언제 쓰는가 ?
- 하나 이상의 객체가 요청을 처리해야 하고, 그 요청 처리자 중 어떤 것이 선행자인지 모를 때. 처리자다 자동으로 확정되어야 함.
- 메시지를 받을 객체를 명시하지 않은 채 여러 객체 중 하나에게 처리를 요청하고 싶을 때.
- 요청을 처리할 수 있는 객체 집합이 동적으로 정의되어야 할 때.
구조
- Hanlder : 요청을 처리하는 인터페이스를 정의하고, 후속 처리자와 연결을 구현. 즉, 연결 고리에 연결된 다음 객체에게 다음 메시지를 보냄.
- ConcreteHandler : 책임져야 할 행동이 있다면 스스로 요청을 처리하여 후속 처리자에 접근할 수 있음. 즉, 자신이 처리할 행동이 있으면 처리하고, 그렇지 않으면 후속 처리자에 다시 처리를 요청함.
- Client : ConcreteHandler 객체에게 필요한 요청을보냄.
결과
- 객체 간의 행동적 결합도가 적어짐
- 다른 객체가 어떻게 요청을 처리하는지 몰라도 됨
- 수신 측, 송신 측이 서로 어떻게 생겼는지 몰라도 됨
- 객체에게 책임을 할당하는 데 유연성을 높일 수 있음
- 메시지 수신이 보장되지는 않음
- 객체간의 연결고리가 잘 정의되지 않았다면, 요청을 처리되지 못한 채로 버려질 수 있음
구현 / 고려 사항
- 후속 처리자들의 연결 고리 구현하기
- 새로운 연결 만들기
- 이미 있는 연결 정보 사용하기
- 후속 처리자 연결하기
- 연결 정보를 정의하기 위해 미리 정의된 참조자가 없다면 직접 정의해야 함
- 처리 요청의 표현부를 정의함
- 요청 자체를 연산 호출로 하드코딩 -> 간편하고 안전하지만 제한적
- 매개변수로 처리를 받아들임 -> 유연하지만 각각에 대응하는 조건문 정의
- 서브클래스를 만들어 식별자를 정의하여 서로 다른 처리를 할 수도 있음 -> 런타임에 타입 정보를 제공한다면 그걸 사용하여도 가능
예제 코드 (1)
- 공통의 인터페이스를 가지고 한 번의 책임연쇄만 가지는 간단한 예제로 구성해봤습니다.
#include "ChainOfResponsibility1.h"
int main()
{
ActionChain action_chain;
ActionMan action_man(&action_chain);
action_man.Fly();
return 0;
}
- actionman이 하늘을 날게 되면 하늘을 난다고 화면에 출력됩니다.
- 사람은 하늘을 날 수 없기 때문에 이 책임에 대해서 actionchain이 대신 처리한 상황입니다.
//ChainOfResponsibility1.h
#pragma once
__interface IAction
{
void Fly();
};
class ActionMan : public IAction
{
public:
ActionMan(IAction* FlyHelper) : FlyHelper(FlyHelper) {}
public:
virtual void Fly() override;
private:
IAction* FlyHelper;
};
class ActionChain : public IAction
{
public:
virtual void Fly() override;
};
- __interface를 사용하여 IAction을 인터페이스로 만들어줍니다. Fly메서드는 따로 구현부가 없는 순수 가상 함수 취급됩니다.
- ActionMan은 생성자에서 다른 IAction 인터페이스를 인자로 받아 책임을 연쇄할 준비를 합니다.
//ChainOfResponsibility1.cpp
#include "ChainOfResponsibility1.h"
#include <iostream>
void ActionMan::Fly()
{
FlyHelper->Fly();
}
void ActionChain::Fly()
{
std::cout << "하늘을 날아요!!" << std::endl;
}
예제 코드 (2)
- 앞서 작성한 예제에서 살짝 응용해서 Fly만이 아닌 다른 Action도 동적으로 정해서 할 수 있게 끔 구성해보겠습니다.
#include "ChainOfResponsibility2.h"
int main()
{
ActionMan action_man;
FlyHelper fly_helper;
JumpHelper jump_helper;
SprintHelper sprint_helper;
action_man.SetAction(&fly_helper);
action_man.DoAction();
action_man.SetAction(&jump_helper);
action_man.DoAction();
action_man.SetAction(&sprint_helper);
action_man.DoAction();
return 0;
}
- actionman은 DoAction을 하기 전에 어떤 행동을 할 지 미리 세팅해놓으면 그 행동을 할 수 있습니다.
//ChainOfResponsibility2.h
#pragma once
enum class EAction
{
Fly,
Sprint,
Jump,
None
};
__interface IAction
{
void DoAction();
EAction WhatAction();
};
class ActionMan : public IAction
{
public:
ActionMan() {}
public:
virtual void DoAction() override;
virtual EAction WhatAction() override;
void SetAction(IAction* ActionHelper);
private:
IAction* ActionHelper = nullptr;
};
class FlyHelper : public IAction
{
public:
virtual void DoAction() override;
virtual EAction WhatAction() override;
};
class JumpHelper : public IAction
{
public:
virtual void DoAction() override;
virtual EAction WhatAction() override;
};
class SprintHelper : public IAction
{
public:
virtual void DoAction() override;
virtual EAction WhatAction() override;
};
- 각각의 행동에 대해 DoAction이라는 공통의 인터페이스를 두었고, DoAction이 서브클래스로 분화하면서 어떤 클래스인지 식별하기 위한 WhatAction 메서드와 EAction 열거형을 두었습니다.
//ChainOfResponsibility2.cpp
#include "ChainOfResponsibility2.h"
#include <iostream>
void ActionMan::DoAction()
{
switch (ActionHelper->WhatAction())
{
case EAction::Fly:
{
std::cout << "날기 전에 준비를 합니다!" << std::endl;
break;
}
case EAction::Jump:
{
std::cout << "점프하기 전에 준비를 합니다!" << std::endl;
break;
}
case EAction::Sprint:
{
std::cout << "달리기 전에 준비를 합니다!" << std::endl;
break;
}
}
if (ActionHelper)
{
ActionHelper->DoAction();
}
}
EAction ActionMan::WhatAction()
{
return EAction::None;
}
void ActionMan::SetAction(IAction* ActionHelper)
{
this->ActionHelper = ActionHelper;
}
void FlyHelper::DoAction()
{
std::cout << "Fly하는 중입니다!!" << std::endl;
}
EAction FlyHelper::WhatAction()
{
return EAction::Fly;
}
void JumpHelper::DoAction()
{
std::cout << "Jump하는 중입니다!!" << std::endl;
}
EAction JumpHelper::WhatAction()
{
return EAction::Jump;
}
void SprintHelper::DoAction()
{
std::cout << "Sprint하는 중입니다!!" << std::endl;
}
EAction SprintHelper::WhatAction()
{
return EAction::Sprint;
}
- actionman은 DoAction을 할 때 어떤 행동이 현재 세팅되어있는지 switch문을 통해 확인할 수 있고, 이 때 사전 작업을 할 수 있습니다.
- 실제 행동은 ActionHelprer의 DoAction에 맡겼습니다.
'디자인 패턴 > 행위' 카테고리의 다른 글
중재자(Mediator) (2) | 2024.01.11 |
---|---|
반복자(Iterator) (1) | 2024.01.04 |
해석자(Interpreter) (1) | 2024.01.04 |
명령(Command) (4) | 2024.01.03 |
행동 패턴 (2) | 2024.01.03 |