객체 간의 혼란스러운 의존성을 줄이는 행동 패턴
객체 간의 직접적인 소통을 제한하고 중재자 객체를 통해서만 협업하도록 함
고객 프로필 생성/관리 대화 상자 가정

form 요소 중 일부는 다른 요소들과 상호작용할 수도 있음
form 요소에 직접적으로 이런 로직들을 구현하면 재사용이 어려워짐
중재자 패턴 - 독립적으로 만들고 싶은 컴포넌트들 간의 직접적인 소통을 멈추고, 적절한 컴포넌트들로 호출을 리다이렉트하는 중재자 객체를 호출해 간접적으로 협업하게끔 함
위 예제에서는 대화 상자 클래스 자체가 중재자 역할을 할 수 있음

가장 두드러지는 변화는 실제 form 요소들에서 일어남
e.g. 제출 버튼
모든 종류의 대화 상자에 대해 공통 인터페이스를 추출해 의존성을 더 느슨하게 할 수 있음
중재자 패턴 - 다양한 객체들 간의 복잡한 관계를 단일 중재자 객체 안에 캡슐화
현실의 관제탑과 유사

1. 컴포넌트 - 비즈니스 로직을 가지는 다양한 클래스들
- 각 컴포넌트는 중재자 인터페이스 타입으로 선언된 중재자에 대한 참조를 가지고 있음
- 컴포넌트는 중재자의 실제 클래스에 대해 알지 못함 → 다른 프로그램에서 다른 중재자에 연결해 재사용 가능
2. 중재자 - 컴포넌트들과의 소통 메서드(주로 단일 알림 메서드)를 선언하는 인터페이스
- 발신자의 클래스와 수신하는 컴포넌트 간 결합이 일어나지 않는 선에서,
컴포넌트들은 자신의 객체를 포함한 어떤 컨텍스트든 해당 메서드의 인수로 전달 가능
3. concrete 중재자 - 다양한 컴포넌트 사이 관계를 캡슐화함
- 종종 관리하는 모든 컴포넌트에 대한 참조를 가지고 있고, 때로는 생명 주기까지 관리함
4. 컴포넌트들은 다른 컴포넌트에 대해 알지 못해야 함
- 만약 컴포넌트에게 또는 컴포넌트 내부에서 중요한 일이 발생하면 중재자에게만 알려야 함
- 중재자는 알림을 받으면 쉽게 발신자를 식별할 수 있고,
이는 응답으로 어떤 컴포넌트가 작동해야 하는지 결정하는 데 충분함
- 컴포넌트 관점에서 블랙박스와 같음 → 발신자는 누가 요청을 처리할지 모르고, 수신자는 누가 요청을 보냈는지 모름
- 중재자 패턴은 클래스들 간의 모든 관계를 별도의 클래스로 추출해
특정 컴포넌트에게 일어나는 변화를 나머지 컴포넌트들로부터 격리 가능
- 중재자 패턴을 적용하면 각 컴포넌트는 다른 컴포넌트들에 대해 알지 못함
- 중재자 객체를 통해 서로 간접적으로는 소통 가능
- 컴포넌트를 다른 앱에서 재사용하려면 새로운 중재자 클래스를 제공해줘야 함
- 모든 컴포넌트들 간의 관계는 중재자 안에 포함되어 있기 때문에,
새 중재자 클래스를 도입하면 컴포넌트들 자체를 변경하지 않고 컴포넌트들이 협업하는 방식을 완전히 바꿀 수 있음
1. 독립적으로 만들면 더 좋을 것 같은 단단히 결합된 클래스들을 식별 (e.g. 유지보수, 재사용 등의 이유)
2. 중재자 인터페이스를 선언하고, 중재자와 컴포넌트들 간의 커뮤니케이션 프로토콜 설명
- 대부분 컴포넌트들로부터 알림을 받는 단일 메서드면 충분
- 이 인터페이스는 컴포넌트 클래스들을 다양한 컨텍스트에서 재사용하고 싶을 때 중요
→ 컴포넌트가 일반 인터페이스를 통해 중재자와 협업한다면, 컴포넌트를 중재자의 다른 구현과도 연결할 수 있음
3. concrete 중재자 클래스 구현
- 중재자 내부에 모든 컴포넌트에 대한 참조를 저장하는 것을 고려 → 중재자 메서드에서 어떤 컴포넌트든 호출 가능
4. 중재자가 컴포넌트 객체의 생성/파괴를 담당하게 할 수도 있음
- 이 경우 중재자가 팩토리나 퍼사드를 닮게 됨
5. 컴포넌트들은 중재자 객체에 대한 참조를 저장해야 함
- 연결은 주로 중재자 객체가 인수로 전달되는 컴포넌트의 생성자에서 수립
6. 다른 컴포넌트의 메서드 대신 중재자 알림 메서드를 호출하도록 컴포넌트들의 코드 변경
- 다른 컴포넌트를 호출하는 코드를 추출해 중재자 클래스로 이동,
중재자가 컴포넌트로부터 알림을 받으면 해당 코드 실행
- SRP - 다양한 객체들 간 커뮤니케이션을 한 곳으로 추출해 이해, 유지보수가 더 쉬워짐
- OCP - 실제 객체를 변경하지 않고 새로운 중재자 도입 가능
- 컴포넌트들 간의 결합 감소
- 개별 객체 재사용 쉬워짐
- 중재자가 시간이 지나며 God Object가 될 수 있음
- 책임 연쇄, 커맨드, 중재자, 옵저버 패턴은 요청의 발신자와 수신자를 연결하는 다양한 방법을 다룸
- 책임 연쇄 패턴 - 요청이 처리될 때까지 잠재적 수신자들로 구성된 동적 체인을 따라 전달함
- 커맨드 - 수신자와 발신자 단방향 커넥션 수립
- 중재자 - 수신자와 발신자 사이의 직접적인 연결을 제거하고 중재자 객체를 통해서만 소통하게 함
- 옵저버 - 수신자들이 동적으로 요청 수신을 구독/구독 취소할 수 있음
- 퍼사드 패턴 & 중재자 패턴 - 비슷한 역할, 밀접하게 결합된 클래스들의 협업을 체계화함
퍼사드 - 하위 시스템 객체들에 대한 단순화된 인터페이스를 정의하지만 새로운 기능을 도입하지 않음,
하위 시스템은 퍼사드의 존재를 모름, 하위 시스템 내부 객체들끼리는 직접 소통 가능
중재자 - 시스템의 컴포넌트들 간의 소통을 중앙화함,
컴포넌트들은 서로 직접 소통하지 않고 중재자 객체에 대해서만 알고 있음
- 중재자와 옵저버의 차이가 종종 애매함
- 대부분 둘 중 한 패턴을 구현하지만 때로는 둘 다 동시에 적용할 수 있음
- 중재자의 목표 - 시스템 컴포넌트들의 집합 간 상호 의존성을 없애는 것, 컴포넌트들은 단일 중재자 객체에 의존
- 옵저버의 목표 - 객체들 간에 동적 단방향 커넥션 수립, 일부 객체는 다른 객체의 종속자 역할 수행
- 옵저버에 의존하는 중재자 패턴의 인기 있는 구현 존재 - 중재자 객체는 발행자,
컴포넌트들은 중재자 이벤트를 구독/구독 취소하는 구독자 역할을 함 → 옵저버와 비슷
- 중재자 패턴은 다른 방식들로 구현 가능
e.g. 모든 컴포넌트들을 같은 중재자 객체에 영구적으로 연결할 수 있음
→ 옵저버와 닮지 않았지만 여전히 중재자 패턴
- 모든 컴포넌트들이 발행자가 돼 서로 동적 연결을 허용하면
중앙화된 중재자 객체는 없고 옵저버의 분산된 집합만 존재하게 됨
/**
* 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.
*/
interface Mediator {
notify(sender: object, event: string): void;
}
/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
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('Mediator 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();
}
}
}
/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent {
protected mediator: Mediator;
constructor(mediator?: Mediator) {
this.mediator = mediator!;
}
public setMediator(mediator: Mediator): void {
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 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');
}
}
/**
* The client code.
*/
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();
// Output.txt
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
참고 자료: Refactoring.guru