디자인 패턴

Mediator 패턴

Keisa 2024. 9. 5. 10:37

중재자는 객체 간의 혼란스러운 의존 관계들을 줄일 수 있는 행동 디자인 패턴입니다. 이 패턴은 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 합니다.

 문제

고객들의 프로필을 만들고 편집하기 위한 대화 상자가 있다고 가정해 봅시다. 이 대화 상자는 텍스트 필드, 체크 상자, 버튼 등과 같은 다양한 양식 컨트롤들로 구성됩니다.

앱이 발전함에 따라 사용자 인터페이스 요소 간의 관계가 혼란스러워질 수 있습니다.

일부 양식 요소들은 다른 요소들과 상호 작용할 수 있습니다. 예를 들어, '저는 개가 있습니다' 확인란을 선택하면 개의 이름을 입력하기 위한 숨겨진 텍스트 필드가 나타날 수 있습니다. 또 다른 예시로 데이터를 저장하기 전에 모든 필드의 값들을 검증해야 하는 제출 버튼이 있습니다.

요소들은 다른 요소들과 많은 관계를 맺을 수 있습니다. 따라서 일부 요소들을 변경하면 다른 요소들에 영향을 줄 수 있습니다.

이 논리를 양식 요소들의 코드 내에서 직접 구현하면 이러한 요소들의 클래스들을 앱의 다른 양식들에서 재사용하기가 훨씬 더 어려워집니다. 예를 들어, 다른 양식 내에서는 위에 언급한 개 관련 확인란 클래스를 사용할 수 없습니다. 왜냐하면 기존 클래스가 개의 이름을 입력하기 위한 텍스트 필드와 결합되어 있기 때문입니다. 이 경우 프로필 양식 렌더링과 관련된 클래스들을 전부 사용하거나 아니면 아예 사용하지 말아야 합니다.

 해결책

중재자 패턴은 서로 독립적으로 작동해야 하는 컴포넌트 간의 모든 직접 통신을 중단한 후, 대신 이러한 컴포넌트들은 호출들을 적절한 컴포넌트들로 리다이렉션하는 특수 중재자 객체를 호출하여 간접적으로 협력하게 하라고 제안합니다. 그러면 컴포넌트들은 수십 개의 동료 컴포넌트들과 결합되는 대신 단일 중재자 클래스에만 의존합니다.

위 프로필 편집 양식 예시에서는 대화 상자 클래스 자체가 중재자 역할을 할 수 있습니다. 아마도 대화 상자 클래스는 이미 자신의 모든 하위 요소들을 인식하고 있으므로 새로운 의존관계들을 도입할 필요가 없을 것입니다.

UI 요소들은 다른 UI 요소들과 중재자 객체를 통해 간접적으로 통신해야 합니다.

가장 중요한 변경들은 실제 양식 요소들에 적용됩니다. 제출 버튼을 살펴보면 이전에는 사용자가 이 버튼을 클릭할 때마다 버튼은 모든 개별 양식 요소들의 값들을 검증해야 했습니다. 이제 제출 버튼이 해야 할 유일한 일은 클릭을 대화 상자에 알리는 것 하나입니다. 이 알림을 받으면 대화 상자는 스스로 검증을 수행하거나 개별 요소들에게 작업을 전달합니다. 따라서 버튼은 여러 개의 양식 요소들에 연결되는 대신 대화 상자 클래스에만 의존하게 됩니다.

여기서 더 나아가 모든 유형의 대화 상자에서 공통 인터페이스를 추출하여 의존성을 더욱 느슨하게 만들 수 있습니다. 이 인터페이스는 모든 양식 요소가 해당 요소들에 발생하는 일​(이벤트)​들을 대화 상자에 알리는 데 사용할 수 있는 알림 메서드를 선언합니다. 이렇게 하면 제출 버튼은 이제 해당 인터페이스를 구현하는 모든 대화 상자들과 작업할 수 있습니다.

그렇게 하면, 중재자 패턴은 단일 중재자 객체 내부의 다양한 객체 간의 복잡한 관계망을 캡슐화할 수 있도록 합니다. 클래스의 의존관계들이 적을수록 해당 클래스를 수정, 확장 또는 재사용하기가 더 쉬워집니다.

 실제상황 적용

항공기 조종사들은 다음에 누가 비행기를 착륙시킬지를 결정할 때 서로 직접 대화하지 않습니다. 모든 통신은 비행기 관제탑을 통해 이루어집니다.

공항 관제 구역으로 들어오거나 그곳을 떠나는 항공기의 조종사들은 서로 직접 통신하지 않습니다. 대신, 그들은 높은 타워에 앉아서 일하는 항공 교통 관제사와 통신합니다. 항공 교통 관제사가 없다면 조종사들은 공항 근처의 모든 비행기의 존재 여부를 인식하고 수십 명의 다른 조종사들로 구성된 위원회와 착륙 우선순위를 논의해야 합니다. 그러면 비행기 충돌 횟수는 아마도 하늘로 치솟을 것입니다.

관제탑은 전체 비행을 관할하지 않습니다. 다만 관련되는 비행기의 수가 조종사에게는 너무 많을 수 있기에 공항 터미널 지역에서만 제약들을 강제하기 위해 존재합니다.

 

의사코드

이 예시에서 중재자 패턴은 버튼들, 확인란들 및 텍스트 레이블들과 같은 다양한 UI 클래스 간의 상호 의존성을 제거하는 데 도움이 됩니다.

UI 대화 상자 클래스들의 구조.

사용자에 의해 작동된 요소는 다른 요소들과 직접 통신하지 않습니다. 대신 이 요소는 중재자에게 이 이벤트​(사건)​에 대해 알리고 중재자에게 해당 알림과 함께 콘텍스트 정보를 전달합니다.

이 예시에서는 인증 대화 상자 전체가 중재자의 역할을 합니다. 이것은 구상 요소들이 어떻게 협력해야 하는지 알고 있으며 그들의 간접적인 의사소통을 촉진합니다. 이벤트에 대한 알림을 받으면 대화 상자는 이벤트를 처리해야 하는 요소를 결정하고 그 결정에 따라 호출을 리다이렉션합니다.

 

#include <iostream>
#include <string>
/**
 * The Mediator interface declares a method used by components to notify the
 * mediator about various events. The Mediator may react to these events and
 * pass the execution to other components.
 */
class BaseComponent;
class Mediator {
 public:
  virtual void Notify(BaseComponent *sender, std::string event) const = 0;
};

/**
 * The Base Component provides the basic functionality of storing a mediator's
 * instance inside component objects.
 */
class BaseComponent {
 protected:
  Mediator *mediator_;

 public:
  BaseComponent(Mediator *mediator = nullptr) : mediator_(mediator) {
  }
  void set_mediator(Mediator *mediator) {
    this->mediator_ = mediator;
  }
};

/**
 * Concrete Components implement various functionality. They don't depend on
 * other components. They also don't depend on any concrete mediator classes.
 */
class Component1 : public BaseComponent {
 public:
  void DoA() {
    std::cout << "Component 1 does A.\n";
    this->mediator_->Notify(this, "A");
  }
  void DoB() {
    std::cout << "Component 1 does B.\n";
    this->mediator_->Notify(this, "B");
  }
};

class Component2 : public BaseComponent {
 public:
  void DoC() {
    std::cout << "Component 2 does C.\n";
    this->mediator_->Notify(this, "C");
  }
  void DoD() {
    std::cout << "Component 2 does D.\n";
    this->mediator_->Notify(this, "D");
  }
};

/**
 * Concrete Mediators implement cooperative behavior by coordinating several
 * components.
 */
class ConcreteMediator : public Mediator {
 private:
  Component1 *component1_;
  Component2 *component2_;

 public:
  ConcreteMediator(Component1 *c1, Component2 *c2) : component1_(c1), component2_(c2) {
    this->component1_->set_mediator(this);
    this->component2_->set_mediator(this);
  }
  void Notify(BaseComponent *sender, std::string event) const override {
    if (event == "A") {
      std::cout << "Mediator reacts on A and triggers following operations:\n";
      this->component2_->DoC();
    }
    if (event == "D") {
      std::cout << "Mediator reacts on D and triggers following operations:\n";
      this->component1_->DoB();
      this->component2_->DoC();
    }
  }
};

/**
 * The client code.
 */

void ClientCode() {
  Component1 *c1 = new Component1;
  Component2 *c2 = new Component2;
  ConcreteMediator *mediator = new ConcreteMediator(c1, c2);
  std::cout << "Client triggers operation A.\n";
  c1->DoA();
  std::cout << "\n";
  std::cout << "Client triggers operation D.\n";
  c2->DoD();

  delete c1;
  delete c2;
  delete mediator;
}

int main() {
  ClientCode();
  return 0;
}

'디자인 패턴' 카테고리의 다른 글

Observer 패턴  (3) 2024.09.05
Memento 패턴  (3) 2024.09.05
iterator 패턴  (1) 2024.09.05
Command 패턴  (0) 2024.09.04
Chain of Responsibility 패턴  (0) 2024.07.25