의도

  • 메시지를 보내는 개체와 이를 받아 처리하는 객체들 간의 결합도를 없애기 위한 패턴. 하나의 요청에 대한 처리가 반드시 한 객체에서만 되지 않고, 여러 객체에게 그 처리 기회를 주려는 것

 

언제 쓰는가 ?

  • 하나 이상의 객체가 요청을 처리해야 하고, 그 요청 처리자 중 어떤 것이 선행자인지 모를 때. 처리자다 자동으로 확정되어야 함.
  • 메시지를 받을 객체를 명시하지 않은 채 여러 객체 중 하나에게 처리를 요청하고 싶을 때.
  • 요청을 처리할 수 있는 객체 집합이 동적으로 정의되어야 할 때.

 

구조

GoF의 디자인 패턴 중 P.301

  • Hanlder : 요청을 처리하는 인터페이스를 정의하고, 후속 처리자와 연결을 구현. 즉, 연결 고리에 연결된 다음 객체에게 다음 메시지를 보냄.
  • ConcreteHandler : 책임져야 할 행동이 있다면 스스로 요청을 처리하여 후속 처리자에 접근할 수 있음. 즉, 자신이 처리할 행동이 있으면 처리하고, 그렇지 않으면 후속 처리자에 다시 처리를 요청함.
  • Client : ConcreteHandler 객체에게 필요한 요청을보냄.

 

결과

  1. 객체 간의 행동적 결합도가 적어짐
    1. 다른 객체가 어떻게 요청을 처리하는지 몰라도 됨
    2. 수신 측, 송신 측이 서로 어떻게 생겼는지 몰라도 됨
  2. 객체에게 책임을 할당하는 데 유연성을 높일 수 있음
  3. 메시지 수신이 보장되지는 않음
    1. 객체간의 연결고리가 잘 정의되지 않았다면, 요청을 처리되지 못한 채로 버려질 수 있음

 

구현 / 고려 사항

  1. 후속 처리자들의 연결 고리 구현하기
    1. 새로운 연결 만들기
    2. 이미 있는 연결 정보 사용하기
  2. 후속 처리자 연결하기
    1. 연결 정보를 정의하기 위해 미리 정의된 참조자가 없다면 직접 정의해야 함
  3. 처리 요청의 표현부를 정의함
    1. 요청 자체를 연산 호출로 하드코딩 -> 간편하고 안전하지만 제한적
    2. 매개변수로 처리를 받아들임 -> 유연하지만 각각에 대응하는 조건문 정의
    3. 서브클래스를 만들어 식별자를 정의하여 서로 다른 처리를 할 수도 있음 -> 런타임에 타입 정보를 제공한다면 그걸 사용하여도 가능

 

예제 코드 (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

+ Recent posts