[Behavioral Patterns] - Mediator

Lee Jeong Min·2022년 1월 16일
0

디자인 패턴

목록 보기
17/23
post-thumbnail

의도

중재자는 특별한 중재자 객체를 통해 간접적으로 통신하게 함으로써 프로그램의 구성 요소들 간의 결합을 줄이는 패턴이다.

중재자는 개별 구성요소가 더 이상 수십 개의 다른 클래스에 종속되지 않으므로 쉽게 수정, 확장 및 재사용할 수 있다.

문제

고객 프로파일을 만들고 편집할 수 있는 dialog가 존재한다고 가정하자. 텍스트 필드, 확인란, 버튼 등과 같은 다양한 폼 컨트롤로 구성된다.

사용자 인터페이스의 요소들 간의 관계는 애플리케이션이 발전함에 따라 혼란스러워 질 수 있다.

양식 요소 중 일부는 다른 요소와 상호 작용할 수 있다. 예를 들어, 'I have a dog' 확인란을 선택하면 개 이름 입력을 위한 숨겨진 텍스트 필드가 나타날 수 있다.

요소들은 다른 요소들과 많은 관계를 가질 수 있다. 따라서 일부 요소의 변경은 다른 요소에 영향을 미칠 수 있다.

이 논리를 폼 요소 코드 내부에 직접 구현하면 앱의 다른 폼에서 재사용하기 엄청 어려워진다. 예를들어, 확인란 클래스는 개의 텍스트 필드에 연결되어 있으므로 재사용하기 어렵다.

해결책

중재자 패턴은 서로 독립적으로 만들려는 구성 요소 간의 모든 직접 통신을 중지할것을 제안한다. 대신에 이러한 요소들은 적절한 요소들로 호출을 리다이렉션하는 특별한 중재자 객체를 호출함으로써 간접적으로 협력해야 한다. 결과적으로 구성 요소는 단일 중재자 클래스에 의존하게 된다.

프로파일 편집 양식 예에서 dialog 클래스 자체가 중재자 역할을 할 수 있다. 대부분의 경우 dialog 클래스는 모든 하위 요소를 이미 알고 있으므로 이 클래스에 새 종속성을 도입할 필요가 없다.

UI 요소는 중재자 객체를 통해 간접적으로 통신해야 한다.

제출 버튼을 생각해 보았을 때, 이전에는 사용자가 버튼을 클릭할 때마다 양식 요소의 값을 검증해야 했지만, 이제 클릭에 대해 dialog에 알리기만 하면 되어 하나의 클래스에 종속시키게 만들 수 있다.

모든 유형의 대화 상자에 대해 공통 인터페이스를 추출하여 종속성을 더욱 느슨하게 만들 수 있다. 인터페이스는 모든 양식 요소가 해당 요소에서 발생하는 이벤트를 dialog에 알리기 위해 사용할 수 있는 알림 방법을 선언한다. 따라서 제출 버튼은 해당 인터페이스를 구현하는 모든 dialog와 함께 작동할 수 있다.

이렇게 하면 중재자 패턴을 사용하여 단일 중재자 객체 내에 있는 다양한 객체간의 복잡한 관계망을 캡슐화할 수 있다. 클래스에 종속성이 적을수록 해당 클래스를 수정, 확장 또는 재사용하기가 쉬워진다.

현실 유사성

착륙순서 및 허가와 같은 모든 통신은 관제탑을 통해 이루어진다.

착륙 허가와 관련하여 관제탑을 통해 결정한다. 만약 이것이 없다면 착륙 우선순위를 직접 논의해야 해서 우왕좌왕 하다가 비행기 추락율을 급증시킬 것이다.

관제탑이 전체 비행을 통제할 필요는 없고 터미널 영역에서 제약을 시행하기 위해서만 존재한다.

구조

  1. 컴포넌트는 일부 비즈니스 논리를 포함하는 다양한 클래스이다. 각 컴포넌트는 중재자 인터페이스의 형식으로 선언된 중재자에 대한 참조를 가진다. 구성 요소가 중재자의 실제 클래스를 인식하지 못하므로 다른 중재자에 연결하여 다른 프로그램에서 구성 요소를 재사용할 수 있다.

  2. 중재자 인터페이스는 일반적으로 단일 통지 방법만 포함하는 구성 요소와의 통신 방법을 선언한다. 에 메서드의 인수로 컴포넌트들은 컨텍스트를 전달할 수 있지만 수신 컴포넌트와 송신자의 클래스 사이에 커플링이 발생하지 않는 방식으로만 가능하다.

  3. 구체적인 중재자는 다양한 구성 요소 간의 관계를 캡슐화한다. 구체적인 중재자는 자신이 관리하는 모든 구성요소에 대한 참조를 보관하고 때로는 라이프사이클을 관리하기도 한다.

  4. 컴포넌트가 다른 구성 요소를 알고 있으면 안된다. 구성 요소 내부 또는 구성 요소에서 중요한 일이 발생할 경우 중재자에게만 알려야 한다. 중재자가 통지를 받으면 발신인을 쉽게 식별할 수 있고, 이는 반환 시 트리거해야 할 구성요소를 결정하는데 충분할 수 있다.
    컴포넌트 관점에서 보면 모두 블랙박스처럼 보인다.(보낸 사람은 누가 요청 처리할지 모르고 받는사람은 애초에 누가 요청을 보냈는지 모름)

적용가능성

  • 일부 클래스가 여러 다른 클래스와 긴밀하게 연결되어 있기 때문에 일부 클래스를 변경하기 어려운 경우 중재자 패턴을 사용한다.

  • 다른 구성요소에 너무 의존하여 다른 프로그램에서 구성요소를 재사용할 수 없는 경우 패턴을 사용하라.

  • 다양한 컨텍스트에서 몇 가지 기본 동작을 재사용하기 위해 수많은 구성 요소 하위 클래스를 만들 때 중재자를 사용하라.

장단점

장점

  • SRP

  • OCP

  • 프로그램의 다양한 구성 요소 간의 커플링을 줄일 수 있다.

  • 개별 구성요소를 보다 쉽게 재사용할 수 있다.

단점

  • God Object가 될 수 있다.

Mediator in TypeScript

TypeScript의 패턴 사용

복잡도: ★★☆

인기: ☆☆☆

사용 예: TS에서 중재자 패턴을 가장 많이 사용하는 것은 앱의 GUI 구성 요소 간의 통신을 용이하게 하는것이다. 중재자의 동의어는 MVC 패턴의 컨트롤러 부분이다.

index.ts

// 중재자 인터페이스는 다양한 이벤트에 대해 중재자에게 통지될
// 컴포넌트들에 의해 사용되어질 메서드들을 선언한다.

interface Mediator {
  notify(sender: object, event: string): void;
}

// 구체적인 중재자는 몇몇 컴포넌트들의 조정을 통해 협력적인 행동을 구현한다.
class ConcreteMediator implements Mediator {
  private component1: Component1;

  private component2: Component2;

  constructor(c1: Component1, c2: Component2) {
    this.component1 = c1;
    this.component1.setMediator(this);
    this.component2 = c2;
    this.component2.setMediator(this);
  }

  public notify(sender: object, event: string): void {
    if (event === 'A') {
      console.log('Meidator reacts on A and triggers following operations:');
      this.component2.doC();
    }

    if (event === 'D') {
      console.log('Mediator reacts on D and triggers following operations:');
      this.component1.doB();
      this.component2.doC();
    }
  }
}

// 기본 컴포넌트는 컴포넌트 객체안의 중재자 인스턴스를 저장하기 위한 기본적인 기능을 제공한다.
class BaseComponent {
  protected mediator: Mediator;

  constructor(mediator: Mediator = null) {
    this.mediator = mediator;
  }

  public setMediator(mediator: Mediator): void {
    this.mediator = mediator;
  }
}

// 구체적인 컴포넌트는 다양한 기능을 제공한다. 또한 다른 컴포넌트에 의존적이지 않고 구체적인 중재자 클래스에 의존하지 않는다.
class Component1 extends BaseComponent {
  public doA(): void {
    console.log('Component 1 does A.');
    this.mediator.notify(this, 'A');
  }

  public doB(): void {
    console.log('Component 1 does B.');
    this.mediator.notify(this, 'B');
  }
}

class Component2 extends BaseComponent {
  public doC(): void {
    console.log('Component 2 does C.');
    this.mediator.notify(this, 'C');
  }

  public doD(): void {
    console.log('Component 2 does D.');
    this.mediator.notify(this, 'D');
  }
}

const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);

console.log('Client triggers operation A');
c1.doA();

console.log('');
console.log('Client triggers operation D.');
c2.doD();

결과

요약

중재자 패턴: 특별한 중재자 객체를 통해 간접적으로 통신하여 결합을 줄임

직접 통신 대신 각 컴포넌트가 중재자 객체를 set할 수 있는 메서드를 갖고, 중재자 설정을 한 후, 특정 작업을 진행 시에 중재자 객체에 notify하여 작업을 시행하도록 만든다.

참고 사이트

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글