책정리/GoF 디자인 패턴

21장 M:N 객체 관계의 M:1 단순화 문제(Mediator 패턴)

GONII 2019. 2. 6. 03:57
    객체지향 설계는 클래스나 객체간의 관계를 정의함으로써 주어진 문제의 해결책을 제시하게 된다. 이때 클래스나 객체간의 관계는 보통 그래프나 네트워크와 같은 형태를 이루게 되는데 이는 객체지향 설계의 특성상 전체 프로그램이 수행해야 하는 작업을 클래스가 객체가 적절히 분산해서 수행하기 때문이다.
    문제는 클래스나 객체간의 관계가 너무 복잡하면 이를 이해하는 것이 어렵고, 구현 또한 힘들어진다는 것이다. 예를 들어 N개의 클래스가 서로 완벽하게 관계를 가진다면 방향성을 고려하였을 N*(N-1)가지의 관계가 존재하게 되는데, 값은 N 커질수록 기하급수적으로 증가하게 된다. 관계의 수가 많아지게 되면 관계를 일일이 이해한다는 것은 거의 불가능하게 된다. 구현 측면에서도 각각의 클래스들이 순환 참조를 해야 하기 때문에 프로그램이 지저분해질 우려가 있다.
    장에서는 객체지향 설계의 특성인 클래스나 객체간의 분산 구조가 M:N 복잡한 관계를 형성함으로 인해 이해도가 떨어지고 구현이나 재사용이 힘들 경우 이를 회피 있는 방법에 대해 알아본다.
    • 문제 사례 설명
    커피 자동 판매기의 동작을 시뮬레이션하는 프로그램 작성
    자동 판매기는 현재 상태를 표기해주기 위한 램프와 종이컵과 원료를 관리하면서 요청이 있을 경우 원료를 배합해서 커피를 이용자에게 내놓은 믹서, 동전을 받아들이고 관리하기 위한 동전박스, 지폐를 받아들이고 관리하는 지폐 박스와 같이 크게 4가지로 구성된다고 가정해보자.
    자동판매기는 다음과 같은 동작을 수행할 것이다.
    먼저 동전이 입력되면 종이컵과 커피 원료가 충분한지 확인하고 믹서를 통해 이용자가 커피를 서비스한다. 과정에서 만약 종이컵이나 커피 원료가 부족한 상태이거나 동전 박스가 있는 상태일 때에는 입력된 동전을 곧바로 반환한다.
    판매기가 고장났을 경우에는 고장 표시 램프를 켜고, 동전이나 지폐를 입력하더라도 곧바로 반환한다.
    원료가 부족할 경우에는 원료가 보충되는 즉시 원료 부족 램프를 끄고 정상적인 서비스를 제공하며, 동전이나 지폐가 경우에는 관리자가 이를 빼내갈 때까지 기다렸다가 해당 램프를 끄고 정상적인 동작을 수행한다.
    해당 프로그램은 자동 판매기를 구성하는 4가지 요소에 맞추어 Lamp, Mixer, CoinBox, BillBox 같은 클래스를 정의할 것이다. 다음으로 이들 클래스의 객체를 생성해서 위에서 언급한 동작들을 수행하게 만들 것이다. 여기서 문제는 위에서 나열한 동작들을 수행하기 위해서는 클래스의 객체들이 서로 어떤 형태로든 커뮤니케이션을 가져야 한다는 것이다.
    객체들간의 다양한 커뮤니케이션이 필요할 경우 어떤 식으로 클래스를 설계해서 적용하는 것이 객체들간의 복잡한 관계를 단순화시키고, 구현을 쉽게 만들며, 개별 클래스 단위의 재사용 효율을 높여줄 있겠는가?
    • 다양한 접근 방법 및 MEDIATOR 패턴
      • 기본적인 방법: M:N 관계 적용 방식
    자동 판매기를 시뮬레이션 하기 위한 클래스로 Lamp, CoinBox, BillBox 정의했다. 그런데 우리가 풀어야 문제는 커피 자동 판매기가 제대로 동작하게 하기 위해서는 이들 4개의 클래스 객체들이 상호 연동해서 동작할 있는 클래스 구조를 만들어주어야 한다는 것이다.
    먼저 4개의 클래스들이 가지는 역할과 인터페이스를 생각해보자.
    Lamp 클래스는 다양한 상태를 표시하는 램프를 켜거나, 끄기 위한 인터페이스를 제공해야 것이다. 램프가 꺼져 있거나, 켜져 있는 정보를 기반으로 자동 판매기의 현재 상태를 알려주는 역할도 담당할 있다.
    Mixer 클래스는 커피 원료와 종이컵의 남은 양을 관리하면서 이용자에게 커피를 서비스하는 역할을 수행하는 것을 시뮬레이션 있다. 커피 원료가 부족하거나 종이컵이 부족할 이를 보충하기 위한 인터페이스도 제공할 있을 것이며, 판매기 자체가 고장난 경우 이를 감지하고 수리하는 것을 시뮬레이션하는 역할도 담당할 있다.
    CoinBox BillBox 클래스는 동전이나 지폐가 입력되거나 반환되는 것을 시뮬레이션할 있는 인터페이스를 제공함으로써 실제 커피 서비스를 시작하게 이벤트를 발생시키는 역할을 담당할 것이다.


    [그림 21-1]에서 보듯이 Lamp 클래스를 제외하고는 각각의 클래스들이 다른 모든 클래스들을 참조하는 형태를 취하고 있다. M개의 클래스가 N개의 클래스와 관계를 가지는 M:N 형태인 것이다. M:N 클래스 관계가 만들어지는 이유는 클래스들이 자신에게 주어진 역할을 완수하기 위해 다른 클래스의 객체를 참조해야 하기 때문이다.
    자동 판매기의 일련의 과정들을 객체간 상호 작용도 형태로 표시하면 [그림 21-2] 같다.


    [그림 21-2] 같은 클래스 구조는 동작에 문제가 없지만 다음과 같은 단점을 가진다.
    가장 단점은 이해하기 어렵다는 점을 있다. 예를 들어 [그림 21-1] 클래스 구조에 위폐 인식기나 관리자에게 자동 판매기의 상태를 통보해주는 역할을 담당하는 클래스 등이 계속해서 추가된다면 어떻게 될까? 아마도 클래스간의 관계는 이상 알아보기 힘들만큼 복잡해질 것이다. 왜냐하면 [그림 21-1] 같은 M:N 관계의 클래스 구조는 각각의 클래스들이 대부분의 다른 클래스들을 참조하는 형태이므로 클래스 구조에 포함되는 클래스의 개수가 늘어날수록 클래스간 관계는 기하급수적으로 증가할 것이기 때문이다.
    두번째 단점은 모든 클래스들이 순환 참조를 하고 있기 때문에 구현 측면에서도 여러 가지 귀찮은 문제가 발생한다는 것이다. 예를 들어 클래스 객체들은 참조할 다른 클래스의 객체가 지정되어야 정상적인 동작을 수행할 있는데, 모든 객체들이 순환 참조를 하고 있기 때문에 단순한 방식으로 참조할 다른 객체들을 지정할 없고 별도로 참조할 객체들을 일일이 지정해야 한다. 객체 생성 한꺼번에 자신이 참조할 객체를 지정해주지 못하고 [소스 21-1] 같이 객체를 모두 생성한 다음에 일일이 참조할 객체를 지정해야 하는 불편함이 있다.
    [소스 21-1] M:N 클래스 구조 상에서 순환 참조에 따른 객체 생성
    void main()
    {
    Lamp lamp;
    Mixer mixer;
    CoinBox coinBox;
    BillBox biilBox;
     
    mixer.SetLamp(&lamp);
    mixer.SetCoinBox(&coinBox);
    mixer.SetBillBox(&billBox);
     
    coinBox.SetLamp(&lamp);
    coinBox.SetMixer(&mixer);
    coinBox.SetBillBox(&billBox);
     
    billBox.SetLamp(&lamp);
    billBox.SetMixer(&mixer);
    billBox.SetCoinBox(&coinBox);
    ...
    }
    [그림 21-1] 같은 M:N 클래스 구조가 가지는 다른 단점은 개별 클래스들을 재사용하고 싶어도 개별 클래스 단위로는 재사용이 불가능 하다는 점이다. 왜냐하면 개별 클래스들은 모두 다른 클래스들을 참조하고 있기 때문에 재사용을 하려면 전체 클래스를 한꺼번에 재사용해야 하기 때문이다.
    • 패턴 활용 방식: MEDIATOR 패턴
    모든 문제는 클래스간 관계가 너무 복잡하다는 데에서 기인한다. 클래스간의 관계가 M:N 형태로 너무 복잡하다는 것이 가장 문제의 원인이라고 있다. 따라서 단점들을 극복하기 위해서는 클래스간의 M:N 관계를 완화시켜줄 방법이 필요하다.
    우선 클래스간 M:N 관계가 성립된 이유는 무엇일까? 그것은 바로 클래스들이 서로 직접적으로 관계를 맺었기 때문이다. 이는 바꾸어 말해 클래스간 M:N 관계를 완화시켜주기 위해서는 클래스간 직접적인 관계를 끊어주면 된다는 것을 의미한다. 그러나 클래스의 객체들은 어떤 형태로든 서로간에 커뮤니케이션을 하거나 연동해서 동작할 필요가 있다.
    그렇다면 클래스간 직접적인 관계를 끊어주면서 서로간의 커뮤니케이션이나 연동 동작이 가능하게 만들어줄 있는 방법은 무엇일까?
    가지 가능한 방법은 바로 클래스간 커뮤니케이션이나 연동 동작을 위한 중재 클래스를 별도로 정의하고 클래스의 객체를 활용해서 간접적으로 클래스간 커뮤니케이션이나 연동 동작이 일어나게 만드는 것이다. [그림 21-1] 클래스 구조에 이런 방식을 적용할 경우 만들어질 있는 클래스 구조는 [그림 21-3] 같다.


    [그림 21-3]에서 보듯이 중재 클래스가 존재할 경우 다른 모든 클래스들은 중재 클래스만 참조하면 된다. 따라서 M:N 클래스 관계는 M:1 같은 형태로 완화될 것이다. 이로 인해 새로운 클래스가 추가된다고 하더라도 클래스간 관계는 클래스 개수에 비례해서 증가하는 정도로 그칠 것이며, 클래스간 관계가 단순해진만큼 전반적인 클래스 설계의 이해도 쉬워질 것이다. 또한 구현 측면에서도 중재 클래스와 주변 클래스들간에만 순환 참조가 존재하기 때문에 순환 참조로 인한 불편이 많이 완화될 것이다. 밖에 재사용 측면에서도 개별 클래스들은 중재 역할을 수행해줄 있는 클래스만 존재하면 어디서든 재사용이 가능하다는 장점을 얻게 된다.
    이처럼 클래스들이 직접적으로 M:N 관계를 맺고 있어 복잡할 , 중재 클래스를 도입해서 클래스간의 직접적인 관계를 끊고, 복잡한 M:N 관계를 M:1 전환시킬 있는 [그림 21-3] 같은 형태의 클래스 구조를 Mediator 패턴이라고 한다. 이때 Mediator 패턴은 복잡한 관계를 단순화시켜 주고, 순환 참조의 범위를 줄여주며, 개별 클래스들의 재사용 효율을 높여주는 효과를 발휘한다.
    • 샘플 코드
    [소스 21-2] Mediator 패턴을 적용한 커피 자동 판매기 시뮬레이션 프로그램
    // lamp.h
    #ifndef LAMP_CLASS_H
    #define LAMP_CLASS_H
     
    class Lamp
    {
    #define LAMP_ON 1
    #define LAMP_OFF 0
    public:
    Lamp();
    void TurnOnOffMixerOut(int onOff);
    void TurnOnOffResourceLack(int onOff);
    void TurnOnOffCoinLack(int onOff);
    void TurnOnOffCoinFull(int onOff);
    void TurnOnOffBillFull(int onOff);
     
    bool IsMixerOut();
    bool IsResourceLack();
    bool IsCoinLack();
    bool IsCoinFull();
    bool IsBillFull();
    private:
    int mixerOutLamp_;
    int resourceLackLamp_;
    int coinLackLamp_;
    int coinFullLamp_;
    int billFullLamp_;
    };
     
    #endif
     
    // lamp.c
    #include "lamp.h"
    using namespace std;
    Lamp::Lamp()
    {
    mixerOutLamp_ = resourceLackLamp_ = coinLackLamp_ = coinFullLamp_ = LAMP_OFF;
    }
     
    void Lamp::TurnOnOffMixerOut(int onOff) { mixerOutLamp_ = onOff; }
    void Lamp::TurnOnOffResourceLack(int onOff) { resourceLackLamp_ = onOff; }
    void Lamp::TurnOnOffCoinLack(int onOff) { coinLackLamp_ = onOff; }
    void Lamp::TurnOnOffCoinFull(int onOff) { coinFullLamp_ = onOff; }
    void Lamp::TurnOnOffBillFull(int onOff) { billFullLamp_ = onOff; }
     
    bool Lamp::IsMixerOut()
    {
    return (mixerOutLamp_ == LAMP_ON) ? true : false;
    }
    bool Lamp::IsResourceLack()
    {
    return (coinLackLamp_ == LAMP_ON) ? true : false;
    }
    bool Lamp::IsCoinLack()
    {
    return (coinLackLamp_ == LAMP_ON) ? true : false;
    }
    bool Lamp::IsCoinFull()
    {
    return (coinFullLamp_ == LAMP_ON) ? true : false;
    }
    bool Lamp::IsBillFull()
    {
    return (billFullLamp_ == LAMP_ON) ? true : false;
    }
     
    // mixer.h
    #ifndef MIXER_CLASS_H
    #define MIXER_CLASS_H
     
    class Mediator;
     
    class Mixer
    {
    #define MIN_MIX_LEVEL 5
    #define MAX_MIX_LEVEL 100
     
    #define MIN_CUP_CNT 1
    #define MAX_CUP_CNT 100
    public:
    Mixer();
    void SetMediator(Mediator* pMediator);
    void Refill(int addLevel);
    void AddCup(int addCnt);
    int TakeOut(int wantedCupCnt = 1);
    void OutOfOrder();
    void Repair();
    protected:
    bool IsVaild();
    private:
    Mediator* pMediator_;
    int mixLevel_;
    int cupCnt_;
    };
     
    #endif
     
    // mixer.c
    Mixer::Mixer()
    {
    pMediator_ = nullptr;
    mixLevel_ = MAX_MIX_LEVEL;
    cupCnt_ = MAX_CUP_CNT;
    }
    void Mixer::SetMediator(Mediator* pMediator) { pMediator_ = pMediator; }
    void Mixer::Refill(int addLevel)
    {
    int oldMixLevel = mixLevel_;
    mixLevel_ += addLevel;
     
    if (!IsVaild())
    return;
     
    if (oldMixLevel < MIN_MIX_LEVEL && mixLevel_ >= MIN_MIX_LEVEL && cupCnt_ >= MIN_CUP_CNT)
    pMediator_->InformResourceState(RECOURCE_LACK);
    }
    void Mixer::AddCup(int addCnt)
    {
    int oldCupCnt = cupCnt_;
    cupCnt_ += addCnt;
     
    if (!IsVaild())
    return;
     
    if (mixLevel_ >= MIN_MIX_LEVEL && oldCupCnt < MIN_CUP_CNT && cupCnt_ >= MIN_CUP_CNT)
    pMediator_->InformResourceState(RESOURCE_OK);
    }
    int Mixer::TakeOut(int wantedCupCnt = 1)
    {
    if (!IsVaild())
    return 0;
     
    int servedCnt = 0;
    while (servedCnt < wantedCupCnt)
    {
    if (mixLevel_ < MIN_MIX_LEVEL || cupCnt_ < MIN_CUP_CNT)
    {
    pMediator_->InformResourceState(RESOURCE_LACK);
    }
     
    mixLevel_--;
    cupCnt_--;
    servedCnt++;
    }
     
    return servedCnt;
    }
    void Mixer::OutOfOrder()
    {
    pMediator_->InformMixerState(MIXER_OUT_OF_ORDER);
    }
    void Mixer::Repair()
    {
    pMediator_->InformMixerState(MIXER_OK);
    }
    bool Mixer::IsVaild()
    {
    if (pMediator_ == nullptr)
    return false;
    else
    return true;
    }
     
    // coinbox.h
    #ifndef COINBOX_CLASS_H
    #define COINBOX_CLASS_H
     
    class Mediator;
     
    class CoinBox
    {
    #define MAX_COIN_CNT 100
    #define MIN_COIN_CNT 10
     
    #define COIN_ACCEPT_MODE 1
    #define COIN_REJECT_MODE 0
     
    public:
    CoinBox();
    void SetMediator(Mediator* pMediator);
     
    void InsertCoin();
    void RejectCoin();
    void PayOutCoin(int payOutCnt);
    void SetCoinBoxMode(int mode);
    protected:
    bool IsValid();
    private:
    Mediator* pMediator_;
    int coinCnt_;
    int mode_;
     
    };
     
    #endif
     
    // coinbox.c
    #include <iostream>
    #include "mediator.h"
    #include "coinbox.h"
     
    CoinBox::CoinBox()
    {
    pMediator_ = nullptr;
    coinCnt_ = 10;
    mode_ = COIN_ACCEPT_MODE;
    }
    void CoinBox::SetMediator(Mediator* pMediator) { pMediator_ = pMediator; }
    void CoinBox::InsertCoin()
    {
    if (!IsValid()
    || pMediator_->IsResourceLack()
    || pMediator_->IsCoinFull()
    || mode_ == COIN_REJECT_MODE)
    {
    RejectCoin();
    return;
    }
    coinCnt_++;
    pMediator_->TakeOutCup();
     
    if (coinCnt_ == MIN_COIN_CNT)
    pMediator_->InformCoinState(COIN_NOT_LACK);
    if (coinCnt_ >= MAX_COIN_CNT)
    pMediator_->InformCoinState(COIN_FULL);
    }
    void CoinBox::RejectCoin()
    {
    cout << "Sorry, Can't Accept Coin Now" << endl;
    }
    void CoinBox::PayOutCoin(int payOutCnt)
    {
    if (!IsValid())
    return;
     
    coinCnt_ -= payOutCnt;
    if (coinCnt_ < MIN_COIN_CNT)
    pMediator_->InformCoinState(COIN_LACK);
    if (coinCnt_ + payOutCnt >= MAX_COIN_CNT)
    pMediator_->InformCoinState(COIN_NOT_FULL);
    }
    void CoinBox::SetCoinBoxMode(int mode) { mode_ = mode; }
    bool CoinBox::IsValid()
    {
    if (pMediator_ == nullptr)
    return false;
    else
    return true;
    }
     
    // billbox.h
    #ifndef BILLBOX_CLASS_H
    #define BILLBOX_CLASS_H
     
    class Mediator;
     
    class BillBox
    {
    #define MAX_BILL_CNT 100
     
    #define BILL_ACCEPT_MODE 1
    #define BILL_REJECT_MODE 0
     
    public:
    BillBox();
    void SetMediator(Mediator* pMediator);
     
    void InsertBill(int wantedCupCnt = 1);
    void RejectBill();
    void TakeOutBill(int takeOutCnt);
    void SetBillBoxMode(int mode);
    protected:
    bool IsValid();
    private:
    Mediator* pMediator_;
    int billCnt_;
    int mode_;
    };
     
    #endif
     
    // billbox.c
    #include <iostream>
    #include "mediator.h"
    #include "billbox.h"
    using namespace std;
     
    BillBox::BillBox()
    {
    pMediator_ = nullptr;
    billCnt_ = 0;
    mode_ = BILL_ACCEPT_MODE;
    }
    void BillBox::SetMediator(Mediator* pMediator) { pMediator_ = pMediator; }
     
    void BillBox::InsertBill(int wantedCupCnt = 1)
    {
    if (!IsValid()
    || pMediator_->IsBillFull()
    || mode_ == BILL_REJECT_MODE)
    {
    RejectBill();
    return;
    }
     
    billCnt_++;
     
    if (wantedCupCnt > 10)
    wantedCupCnt = 10;
    int servedCnt = pMediator_->TakeOutCup(wantedCupCnt);
    pMediator_->PayOutCoin(10 - servedCnt);
     
    if (wantedCupCnt > servedCnt)
    cout << "Sorry, No More Served" << endl;
     
    if (billCnt_ >= MAX_BILL_CNT)
    pMediator_->InformBillState(BILL_FULL);
    }
    void BillBox::RejectBill()
    {
    cout << "Sorry, Can't Accept Bill now" << endl;
    }
    void BillBox::TakeOutBill(int takeOutCnt)
    {
    if (!IsValid())
    return;
     
    billCnt_ -= takeOutCnt;
     
    if (billCnt_ < MAX_BILL_CNT)
    pMediator_->InformBillState(BILL_NOT_FULL);
    }
    void BillBox::SetBillBoxMode(int mode) { mode_ = mode; }
    bool BillBox::IsValid()
    {
    if (pMediator_ == nullptr)
    return false;
    else
    return true;
    }
     
     
    // mediator.h
    #ifndef MEDIATOR_CLASS_H
    #define MEDIATOR_CLASS_H
     
    class Lamp;
    class Mixer;
    class CoinBox;
    class BillBox;
     
    class Mediator
    {
    #define MIXER_OUT_OF_ORDER 0
    #define MIXER_OK 1
     
    #define RESOURCE_LACK 0
    #define RESOURCE_OK 1
     
    #define COIN_LACK 0
    #define COIN_NOT_LACK 1
    #define COIN_FULL 2
    #define COIN_NOT_FULL 3
     
    #define BILL_FULL 0
    #define BILL_NOT_FULL 1
    public:
    Mediator(Lamp* pLamp, Mixer* pMixer, CoinBox* pCoinBox, BillBox* pBillBox);
     
    bool IsResourceLack();
    bool IsCoinLack();
    bool IsCoinFull();
    bool IsBillFull();
     
    void InformMixerState(int state);
    void InforResourceState(int state);
    void InformCoinState(int state);
    void InformBillState(int state);
     
    int TakeOutCup(int tackOutCnt = 1);
    void PayOutCoin(int payOutCnt);
    private:
    Lamp* pLamp_;
    Mixer* pMixer_;
    CoinBox* pCoinBox_;
    BillBox* pBillBox_;
    };
     
    #endif
     
    // mediator.c
     
    Mediator::Mediator(Lamp* pLamp, Mixer* pMixer, CoinBox* pCoinBox, BillBox* pBillBox)
    :pLamp_(pLamp), pMixer_(pMixer), pCoinBox_(pCoinBox), pBillBox_(pBillBox)
    {}
     
    bool Mediator::IsResourceLack() { return pLamp_->IsResourceLack(); }
    bool Mediator::IsCoinLack() { return pLamp_->IsCoinLack(); }
    bool Mediator::IsCoinFull() { return pLamp_->IsCoinFull(); }
    bool Mediator::IsBillFull() { return pLamp_->IsBillFull(); }
     
    void Mediator::InformMixerState(int state)
    {
    if (state == MIXER_OUT_OF_ORDER)
    {
    pLamp_->TurnOnOffMixerOut(LAMP_ON);
    pCoinBox_->SetCoinBoxMode(COIN_REJECT_MODE);
    pBillBox_->SetBillBoxMode(BILL_REJECT_MODE);
    }
    else if (state == MIXER_OK)
    {
    pLamp_->TurnOnOffMixerOut(LAMP_OFF);
    pCoinBox_->SetCoinBoxMode(COIN_ACCEPT_MODE);
    pBillBox_->SetBillBoxMode(BILL_ACCEPT_MODE);
    }
    else
    {}
    }
    void Mediator::InforResourceState(int state)
    {
    if (state == RESOURCE_LACK)
    pLamp_->TurnOnOffResourceLack(LAMP_ON);
    else if (state == RESOURCE_OK)
    pLamp_->TurnOnOffResourceLack(LAMP_OFF);
    else
    { }
    }
    void Mediator::InformCoinState(int state)
    {
    if (state == COIN_LACK)
    pLamp_->TurnOnOffCoinLack(LAMP_ON);
    else if (state == COIN_NOT_LACK)
    pLamp_->TurnOnOffCoinLack(LAMP_OFF);
    else if (state == COIN_FULL)
    pLamp_->TurnOnOffCoinFull(LAMP_ON);
    else if (state == COIN_NOT_FULL)
    pLamp_->TurnOnOffCoinFull(LAMP_OFF);
    else
    {
    }
    }
    void Mediator::InformBillState(int state)
    {
    if (state == BILL_FULL)
    pLamp_->TurnOnOffBillFull(LAMP_ON);
    else if (state == BILL_NOT_FULL)
    pLamp_->TurnOnOffBillFull(LAMP_OFF);
    else
    {
    }
    }
     
    int Mediator::TakeOutCup(int tackOutCnt = 1) { return pMixer_->TakeOut(tackOutCnt); }
    void Mediator::PayOutCoin(int payOutCnt) { return pCoinBox_->PayOutCoin(payOutCnt); }
     
    // main.c
    #include <iostream>
    #include "lamp.h"
    #include "mixer.h"
    #include "coinbox.h"
    #include "billbox.h"
    #include "mediator.h"
    using namespace std;
     
    void main()
    {
    Lamp lamp;
    Mixer mixer;
    CoinBox coinBox;
    BillBox billBox;
     
    Mediator mediator(&lamp, &mixer, &coinBox, &billBox);
    mixer.SetMediator(&mediator);
    coinBox.SetMediator(&mediator);
    billBox.SetMediator(&mediator);
     
    for (int i = 0; i < 90; i++)
    coinBox.InsertCoin();
     
    // 고장 & 수리
    mixer.OutOfOrder();
    coinBox.InsertCoin();
    billBox.InsertBill();
    mixer.Repair();
     
    billBox.InsertBill(8);
    }
    • 구현 관련 사항
    Mediator 클래스도 상속 관계에 의해 하위 클래스들을 가질 있다. 다만 경우 상속 관계에 놓인 클래스들은 동일한 외부 인터페이스를 가져야 것이다. 만약 모든 클래스가 종류의 Mediator 객체만 참조한다면 특별히 이와 같은 상속 관계를 정의할 필요는 없다.
    다음으로 Mediator패턴 적용 고려해볼만한 사항은 Mediator 객체와 연동하고 있는 객체들에게 주요 이벤트가 발생했을 이를 Mediator 객체에게 어떤 식으로 알리도록 것인가에 대한 것이다.
    가지 생각해볼만한 방법은 Mediator 객체에게 특정 이벤트가 발생했음을 알려주기 위한 인터페이스를 별도로 정의하는 것이다. 경우 Mediator 연동한 객체들은 보다 직접적으로 이벤트의 발생을 Mediator 객체에게 알릴 있으며, 함수 인자로 자기 자신에 대한 포인터를 전달함으로써 실제 이벤트가 일어난 객체를 Mediator 객체가 참조할 있게 만들 있다.
    다른 방법으로는 23장에서 언급할 Observer 패턴을 활용해서 Mediator 객체에게 이벤트의 발생을 알려주는 것이다. 이때 Mediator 객체는 별도의 인터페이스를 정의하지 않고 공통으로 사용되는 인터페이스를 통해 이벤트의 발생을 전달받는 식이 것이다.
    • MEDIATOR 패턴 정리
     Mediator 패턴이 유용한 경우
    • 객체들간에 복잡한 상호 작용이 존재해서 서로간의 의존 관계가 불명확하고 이해하기 힘들
    • 하나의 객체가 다른 많은 객체들을 참조하거나 상호 작용하고 있어 재사용이 힘들
    • 여러 클래스에 분산된 행위에 대해 많은 하위 클래스를 많이 생성하지 않고 커스터마이징해야
    Mediator 패턴의 .단점 다른 패턴과의 관계
    • 여러 객체들에게 분산될 행위를 곳으로 모아주는 역할을 한다. 따라서 행위의 확장이 필요할 경우 Mediator 클래스에 대해서만 새로운 하위 클래스를 정의하면 되고, 나머지 클래스들은 그대로 재사용이 가능하다.
    • Colleague 클래스들간의 결합도를 줄여준다. 또한 Mediator 클래스와 Colleague 클래스들은 독립적으로 변경, 재사용이 가능하다.
    • M:N관계를 M:1 관계로 변경시켜주며 이를 통해 객체들간의 관계를 훨씬 쉽게 이해하고, 관리, 확장할 있게 해준다.
    • 객체들간의 상호 연동 관계를 Mediator 객체 내부에 숨김으로써 설계 객체의 행위와 무관하게 객체간 상호 연동 관계를 다룰 있도록 해준다.
    • 객체들간의 상호 연동 관계를 하나의 객체가 책임지고 처리하는 방식이다. 따라서 이런 역할을 맡은 중개 객체는 다른 객체들보다 복잡해질 있으며, 때에 따라 유지, 보수가 힘들어질 있다.
    • Facade 패턴과는 객체들간의 연동 방식에서 서로 다르다. Facade패턴은 Facade 객체를 통해 외부에서 서브시스템 내의 객체를 사용하는 단방향 형태의 연동이지만, Mediator 패턴은 중개 객체를 통해 객체들간의 양방향 연동이 가능하다.
    • Observer 패턴을 활용해서 객체들간의 연동 관계를 처리할 수도 있다.
반응형