Mediator[Design Pattern]

SnowCat·2023년 3월 21일
0

Design Pattern

목록 보기
17/23
post-thumbnail

의도

  • 중재자 -> 객체 간의 직접적인 의존을 제한하고 중재자를 통하도록 하는 행동 디자인 패턴

문제

  • 프로필을 만들고 편집하기 위한 대화 상자가 있다고 가정해보자. 이미 구조가 상당히 복잡한 상태이다.
  • 일부 요소들은 다른 요소들과 상호작용할 수 있다. 가령 "집에 개가 있다"라는 조건을 만족하면 개의 이름을 입력하기 위한 새로운 필드창이 나타날 수도 있다.
  • 그런데 이를 만약 코드 내에 직접 작성한면, 대화 상자 컴포넌트는 다른 곳에서 재활용하기 매우 어려워질 것이다.

해결책

  • 서로 독립적으로 작동하는 컴포넌트 간의 직접 통신을 중단하고 호출을 적절한 컴포넌트로 리다이렉트하기
  • 중재자라 불리는 컴포넌트를 만들어 이 컴포넌트를 통해서만 소통하게 함으로써 코드의 복잡성을 줄일 수 있음
  • 더 나아가면 여러 유형의 컴포넌트에서 공통적으로 사용되는 인터페이스를 추출해 하나의 클래스에 집어넣어 의존성을 줄일 수 있음
  • 중재자 패턴은 객체 내부의 복잡한 관계를 정리하고 이를 캡슐화 할 수 있도록 해줌

구조

/*
 * 중재자 인터페이스
 * 인터페이스에서는 보통 통신을 위한 하나의 메서드만들 선언함
 * 컴포넌트는 수신자와 발신자 클래스간의 결합이 되지 않는다는 전제하에 어떠한 값이든 메서드에 전달 가능
 */
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('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();
        }
    }
}

/**
 * 컴포넌트 클래스에서 중재자 클래스 참조를 가지게 됨
 * 컴포넌트 클래스가 실제 중재자 클래스가 무엇을 하는지는 알 수 없기 때문에 
 */
class BaseComponent {
    protected mediator: Mediator;

    constructor(mediator?: Mediator) {
        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();
/*
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
*/

console.log('');
console.log('Client triggers operation D.');
c2.doD();
/*
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
*/

적용

  • 일부 클래스들이 다른 클래스들과 단단하게 결합하여 변경하기 어려울 때 사용
    특정 컴포넌트에 대한 모든 변경을 나머지 컴포넌트로부터 고립해 클래스 간의 모든 관계를 분리할 수 있음
    이를 통해 컴포넌트 재사용을 용이하게 할수도 있음
  • 몇가지 비즈니스 로직이 여러 컴포넌트로 복제되고 있을 때 사용
    중재자를 하나 만들어 여러 컴포넌트들이 중재자 코드를 사용할 수 있도록 하게 변경

구현 방법

  1. 클래스가 비즈니스 로직과 단단히 결합되어있는지 확인
  2. 중재자 인터페이스를 선언하고 중재자와 컴포넌트 간의 통신 프로토콜을 단일 메서드로 선언
  3. 구상 중재자 클래스를 생성하고 연결, 이 때 구상 클래스 내부에 컴포넌트에 대한 참조를 넣어주는 것이 좋음
    필요하다면 컴포넌트 객체의 생성, 삭제를 담당할 수 있으며, 이 경우 중재자는 팩토리, 퍼사드와 유사해지게 됨
  4. 클라이언트 코드에서 다른 컴포넌트의 메서드 대신 중재자의 알림 세서드를 호출하도록 컴포넌트 코드를 고치기

장단점

  • 다양한 컴포넌트 간의 통신을 한 곳으로 추출해 단일 책임 원칙 준수
  • 실제 컴포넌트 변경 없이 새로운 중재자를 도입할수 있기 때문에 개방, 폐쇄 원칙을 준수함
  • 프로그램의 다양한 컴포넌트 간의 결합도를 줄일 수 있고, 더 쉽게 컴포넌트를 재사용할 수 있게 됨
  • 중재자가 과도하게 커질 우려가 있음

출처:
https://refactoring.guru/ko/design-patterns/mediator

profile
냐아아아아아아아아앙

0개의 댓글