책정리/GoF 디자인 패턴

23장 One Source Multiple Use 문제(Observer 패턴)

GONII 2019. 2. 7. 20:40
    장에서는 하나의 원본 데이터를 동시에 참조하고 있는 여러 개의 서로 다른 표현 양식간에 데이터 값의 변화를 즉시 반영할 있는 방법에 대해 알아본다.
    • 문제 사례 설명
    동일한 데이터를 여러 형태로 표현하였을 , 어느 표현 양식의 데이터 값에 변경이 생기면 다른 모든 표현 양식에도 변경된 데이터 값을 즉시 반영해줄 있는 설계방법은 어떤 것이 있을까?
    • 다양한 접근 방법 및 OBSERVER 패턴
    동일한 데이터를 여러 곳에서 사용하는 경우를 One Source Multiple Use라고 부르는데 우리가 해결해야 하는 문제는 서로 다른 표현 양식간에 데이터의 일관성을 유지시켜주는 방법에 대한 것이다.


    • 기본적인 방법: 원본 데이터에 대한 직접적인 참조 방식
    가장 간단히 생각해볼 있는 방법은 모든 표현 양식들이 동일한 데이터를 직접 참조하게 만드는 것이다.
    그러나 방식은 다음과 같이 크게 가지 측면에서 적절하지 않다.
    먼저 방식은 어느 표현 양식에서 데이터 값이 바뀔 경우 데이터 자체는 변경되지만, 다른 표현 양식들에게 데이터 값이 바뀌었으니 바뀐 데이터 값을 참조해서 그래프를 다시 그리라는 메시지까지는 전달하지 못한다. 따라서 방식을 적용하더라도 다른 표현 양식이 데이터 값을 다시 참조하지 않는   변경된 데이터 값이 즉시 반영되지 못하는 문제가 존재한다.
    다음으로 방식은 형태로 저장된 데이터 값을 직접 참조하는 형태인데 이는 모든 표현 양식들이 형태로 데이터 값을 저장하는 방법을 알아야 한다는 것을 의미한다. 형태로 데이터 값을 저장하고 있는 객체의 입장에서는 정보 은닉이 깨뜨려지는 셈이다. 이는 만약 형태로 데이터 값을 저장하는 객체의 내부 데이터 구조가 변경될 경우 표현 양식과 관련된 모든 객체에서 데이터 값을 참조하는 방식을 변경시켜야 하는 문제를 안고 있다.
    • 패턴 활용 방식: OBSERVER 패턴
    [그림 23-2] 같은 형태에서 어느 표현 양식이 데이터 값을 변경할 경우 이를 다른 모든 표현 양식에 즉시 반영시켜줄 있는 방법은 무엇일까?
    가지 생각해볼 있는 방법은 데이터 값이 변경된 표현 양식이 다른 모든 표현 양식에게 변경된 데이터 값을 반영하도록 직접 메시지를 전송하는 것이다. 그러나 방식은 데이터 값이 변경된 표현 양식이 다른 모든 표현 양식들에 대해 알고 있어야 한다는 문제가 있다. 모든 표현 양식들이 서로에 대해 사전에 알고 있어야만 데이터 값에 변경이 있을 경우 이를 다른 표현 양식들에게 전달할 있을 것인데, 이를 위해서는 모든 표현 양식들이 복잡하게 서로를 참조하는 관계를 유지해야 하는 문제가 있다.
    이런 문제 없이 어느 표현 양식에서 변경된 데이터 값을 다른 모든 표현 양식에게 알려줄 있는 방법으로는 21장의 Mediator 패턴에서처럼 매개 객체를 사용하는 것이다. 데이터 값이 변경된 표현 양식은 매개 객체에게 데이터 값이 변경되었음을 통보하고, 매개 객체가 이를 전체 표현 객체에게 알려주는 방식이다. [그림 23-2]에서 이런 매개 객체로 가장 적합한 것은 원본 데이터를 저장, 관리하는 객체일 것이다. 왜냐하면 매개 객체는 기본적으로 모든 객체의 중심에 놓여 있어야 하는데, [그림 23-2]에서 이런 위치에 놓여 있는 객체는 원본 데이터를 저장, 관리하는 객체뿐이기 때문이다. 한편 이때 매개 객체는 변경된 데이터 값을 모든 표현 객체에게 알려주기 위해 표현 객체들에 대한 정보를 관리하고 있어야 한다. 이를 위해 매개 객체는 내부의 원본 데이터를 참조하려는 모든 표현 객체에 대해 먼저 자신에게 등록하도록 요청할 것이며, 이를 위한 인터페이스도 제공할 것이다.
    이처럼 원본 데이터를 저장, 관리하는 객체를 매개로 하면 데이터 값의 변경이 있을 경우 이를 즉시 모든 표현 양식에 반영하는 것이 가능하게 된다. 그렇다면 다음으로 원본 데이터를 저장, 관리하는 객체의 정보 은닉이 깨뜨려지지 않게 하려면 어떻게 하면 될까?
    정보 은닉이 깨뜨려지지 않게 하는 가장 좋은 방법은 객체 내부의 자료구조와는 무관하게 표현 객체들이 원본 데이터의 값을 참조할 있게 표준화된 자료구조를 제공하는 것이다. 원본 데이터의 값을 완벽히 표현할 있는 자료구조를 별도로 정의하고 객체 내부의 자료구조와는 무관하게 별도로 정의된 자료구조를 통해 객체간의 정보 교류가 이루어지도록 만들어주면 되는 것이다.



    [그림 23-3] 클래스 구조에 의해 생성된 객체들이 동작하는 형태를 살펴보면 [그림 23-4] 같다. 그림에서 aBarGraph 객체와 aLineGraph 객체는 처음 생성되면서 aScoreData 객체에게 자기 자신을 등록하게 된다. 이렇게 등록이 이루어진 상태에서 aBarGraph 객체의 데이터 값이 변경되면 사실이 SetScore() 멤버 함수를 통해 aScoreData 객체에게 전달되게 된다. 한편 aScoreData 객체는 데이터 값이 변경되었다는 사실을 통보받으면 변경된 데이터의 원본 데이터 값을 수정해줌과 동시에 Notify() 멤버 함수를 통해 변경된 데이터 값을 다른 객체들이 반영하도록 통보하게 된다. 이때 Notify() 멤버 함수가 다른 객체들에게 데이터 값이 변경되었음을 통보하는 방식은 내부적으로 관리하고 있던 observer_ 데이터 멤버에 저장된 객체들에 대해 Update() 멤버 함수를 불러주는 방식이 된다. 이렇게 Update() 멤버 함수가 불리게 되면 aBarGraph aLineGraph 객체는 aScoreData 객체의 GetScoreList() 멤버 함수를 호출해서 변경된 정보를 받아가게 되는 것이다.
    이처럼 One Source Multiple Use 상황에서 여러 표현 양식간에 데이터의 일관성을 유지하기 쉽게 설계된 [그림 23-3] 같은 클래스 구조를 Observer 패턴이라고 한다.
    • 샘플 코드
    [소스 23-1] Observer 패턴에 의한 학생 성적 표현 양식간의 데이터 일관성 유지 소스
    // scorecard.h
    #ifndef SCORECARD_H
    #define SCORECARD_H
     
    #include <string>
    using namespace std;
     
    #define MOTHER_LANG_SUBJECT 1
    #define MATH_SUBJECT 2
    #define ENGLISH_SUBJECT 3
     
    class ScoreCard
    {
    public:
    ScoreCard()
    {
    motherLangScore_ = mathScore_ = englishScore_ = 0;
    }
    void SetScore(int motherLangScore, int englishScore, int mathScore)
    {
    motherLangScore_ = motherLangScore;
    englishScore_ = englishScore;
    mathScore_ = mathScore;
    }
    string name_;
    int motherLangScore_;
    int englishScore_;
    int mathScore_;
    };
     
    #endif
     
    // subject.h
    #ifndef SUBJECT_H
    #define SUBJECT_H
     
    #include <stirng>
    #include <list>
    #include "scorecard.h"
     
    using namespace std;
     
    class Observer;
     
    class Subject
    {
    public:
    virtual ~Subject() = 0;
    virtual void Attach(Observer* pObj);
    virtual void Detach(Observer* pObj);
    virtual void Notify();
    protected:
    list<Observer*> observer_;
    };
     
    class ScoreData : public Subject
    {
    public:
    void AddScore(ScoreCard* pScore);
    void RemoveScore(ScoreCard* pScore);
     
    list<ScoreCard*> GetScoreList();
    void SetScore(string name, int subjectType, int score);
    private:
    list<ScoreCard*> scoreList_;
    };
     
    #endif
     
    // subject.c
    #include <string>
    #include <list>
    #include <algorithm>
    #include "observer.h"
    #include "subject.h"
    using namespace std;
     
    Subject::~Subject() {}
     
    void Subject::Attach(Observer* pObj)
    {
    observer_.push_front(pObj);
    }
     
    void Subject::Detach(Observer* pObj)
    {
    list<Observer*>::iterator iter;
    for (iter = observer_.begin(); iter != observer_.end(); iter++)
    {
    if (*iter == pObj)
    iter = observer_.erase(iter);
    }
    }
     
    void Subject::Notify()
    {
    list<Observer*>::iterator iter;
    for (iter = observer_.begin(); iter != observer_.end(); iter++)
    {
    (*iter)->Update();
    }
    }
    //-----------------------------
    void ScoreData::AddScore(ScoreCard* pScore)
    {
    scoreList_.push_front(pScore);
    Notify();
    }
     
    void ScoreData::RemoveScore(ScoreCard* pScore)
    {
    list<ScoreCard*>::iterator iter;
    iter = find(scoreList_.begin(), scoreList_.end(), pScore);
    if (iter != scoreList_.end())
    {
    ScoreCard* pTempScore = *iter;
    delete pTempScore;
    scoreList_.erase(iter);
    Notify();
    }
    }
     
    list<ScoreCard*> ScoreData::GetScoreList() { return scoreList_; }
     
    void ScoreData::SetScore(string name, int subjectType, int score)
    {
    list<ScoreCard*>::iterator iter;
     
    for (iter = scoreList_.begin(); iter != scoreList_.end(); iter++)
    {
    ScoreCard* pTempScore = *iter;
    if (pTempScore->name_ == name)
    {
    switch (subjectType)
    {
    case MOTHER_LANG_SUBJECT:
    pTempScore->motherLangScore_ = score;
    break;
    case ENGLISH_SUBJECT:
    pTempScore->englishScore_ = score;
    break;
    case MATH_SUBJECT:
    pTempScore->mathScore_ = score;
    break;
    }
    Notify();
    break;
    }
    }
    }
     
    // observer.h
    #ifndef OBSERVER_H
    #define OBSERVER_H
     
    #include <string>
    #include "scorecard.h"
    using namespace std;
     
    #define MAX_STUDENT_NUM 10
     
    class Subject;
    class ScoreData;
     
    class Observer
    {
    public:
    virtual void Update() = 0;
    };
     
    class BarGraph : public Observer
    {
    public:
    BarGraph(ScoreData* pScoreData);
    void Update();
    void PrintOut();
    void ChangeScore(string name, int subjectType, int score);
    private:
    ScoreData* pScoreData_;
    string name_[MAX_STUDENT_NUM];
    int motherLangScore_[MAX_STUDENT_NUM];
    int englishScore_[MAX_STUDENT_NUM];
    int mathScore_[MAX_STUDENT_NUM];
    };
     
    class PieChart : public Observer
    {
    public:
    PieChart(string student, ScoreData* pScoreData);
    void Update();
    void PrintOut();
    void ChangeScore(string name, int subjectType, int score);
    private:
    ScoreData* pScoreData_;
    string name_;
    int motherLangScore_;
    int englishScore_;
    int mathScore_;
    };
     
    class LineGraph : public Observer
    {
    public:
    LineGraph(ScoreData* pScoreData);
    void Update();
    void PrintOut();
    void ChangeScore(string name, int subjectType, int score);
    private:
    ScoreData* pScoreData_;
    string name_[MAX_STUDENT_NUM];
    int motherLangScore_[MAX_STUDENT_NUM];
    int englishScore_[MAX_STUDENT_NUM];
    int mathScore_[MAX_STUDENT_NUM];
    };
    #endif
     
    // observer.c
    #include <iostream>
    #include "subject.h"
    #include "observer.h"
    using namespace std;
     
    BarGraph::BarGraph(ScoreData* pScoreData)
    : pScoreData_(pScoreData)
    {
    pScoreData_->Attach(this);
    }
     
    void BarGraph::Update()
    {
    list<ScoreCard*> scoreList;
    scoreList = pScoreData_->GetScoreList();
     
    int nthStudent = 0;
    list<ScoreCard*>::iterator iter;
    for (iter = scoreList.begin(); iter != scoreList.end(); iter++)
    {
    ScoreCard* pTempScore = *iter;
    name_[nthStudent] = pTempScore->name_;
    motherLangScore_[nthStudent] = pTempScore->motherLangScore_;
    englishScore_[nthStudent] = pTempScore->englishScore_;
    mathScore_[nthStudent] = pTempScore->mathScore_;
    nthStudent++;
    }
    }
     
    void BarGraph::PrintOut()
    {
    for (int i = 0; i < MAX_STUDENT_NUM; i++)
    {
    if (name_[i].empty())
    break;
     
    cout << name_[i] << "=>" << motherLangScore_[i] " : "
    << englishScore_[i] << " : " << mathScore_[i] << endl;
    }
    }
     
    void BarGraph::ChangeScore(string name, int subjectType, int score)
    {
    pScoreData_->SetScore(name, subjectType, score);
    }
    //-------------------------------------------------
     
    PieChart::PieChart(string student, ScoreData* pScoreData)
    : pScoreData_(pScoreData)
    {
    name_ = student;
    pScoreData_->Attach(this);
    }
     
    PieChart::Update()
    {
    list<ScoreCard*> scoreList;
    scoreList = pScoreData_->GetScoreList();
     
    list<ScoreCard*>::iterator iter;
    for (iter = scoreList.begin(); iter != scoreList.end(); iter++)
    {
    ScoreCard* pTempScore = *iter;
     
    if (name_ == pTempScore->name_)
    {
    motherLangScore_ = pTempScore->motherLangScore_;
    englishScore_ = pTempScore->englishScore_;
    mathScore_ = pTempScore->mathScore_;
    break;
    }
    }
    }
     
    void PieChart::PrintOut()
    {
    cout << name_ << "=>" << motherLangScore_
    << ":" << englishScore_ << ":" << mathScore_ << endl;
    }
     
    void PieChart::ChangeScore(string name, int subjectType, int score)
    {
    pScoreData_->SetScore(name, subjectType, score);
    }
     
    //-------------------------------------------------
     
    LineGraph::LineGraph(ScoreData* pScoreData)
    :pScoreData_(pScoreData)
    {
    pScoreData_->Attach(this);
    }
     
    void LineGraph::Update()
    {
    list<ScoreCard*> scoreList;
    scoreList = pScoreData_->GetScoreList();
     
    int nthStudent = 0;
    list<ScoreCard*>::iterator iter;
    for (iter = scoreList.begin(); iter != scoreList.end(); iter++)
    {
    ScoreCard* pTempScore = *iter;
    name_[nthStudent] = pTempScore->name_;
    motherLangScore_[nthStudent] = pTempScore->motherLangScore_;
    englishScore_[nthStudent] = pTempScore->englishScore_;
    mathScore_[nthStudent] = pTempScore->mathScore_;
    nthStudent++;
    }
    }
     
    void LineGraph::PrintOut()
    {
    for (int i = 0; i < MAX_STUDENT_NUM; i++)
    {
    if (name_[i].empty())
    break;
     
    cout << name_[i] << "=>" << motherLangScore_[i]
    << ":" << englishScore_[i] << ":" << mathScore_[i] << endl;
    }
    }
     
    void LineGraph::ChangeScore(string name, int subjectType, int score)
    {
    pScoreData_->SetScore(name, subjectType, score);
    }
     
    // main.c
    #include <iostream>
    #include "scorecard.h"
    #include "subject.h"
    #include "observer.h"
    using namespace std;
     
    void main()
    {
    ScoreData termScore;
    BarGraph bar(&termScore);
    LineGraph line(&termScore);
     
    ScoreCard stu1, stu2, stu3;
    stu1.name_ = "aaa";
    stu1.SetScore(70, 60, 90);
     
    stu2.name_ = "bbb";
    stu2.SetScore(96, 90, 85);
     
    stu3.name_ = "ccc";
    stu3.SetScore(96, 90, 23);
     
    termScore.AddScore(&stu1);
    termScore.AddScore(&stu2);
    termScore.AddScore(&stu3);
     
    cout << "-------------------------" << endl;
    bar.PrintOut();
     
    cout << "-------------------------" << endl;
    line.PrintOut();
     
    bar.ChangeScore("aaa", ENGLISH_SUBJECT, 33);
     
    cout << "-------------------------" << endl;
    bar.PrintOut();
    cout << "-------------------------" << endl;
    line.PrintOut();
    }
    • 구현 관련 사항
    Subject 객체가 자신에 의해 영향 받는 Observer 객체들을 관리하는 방식에 대해 생각해보자. 가장 간단한 방법은 [소스 23-1]에서도 보았듯이 Subject 객체 내에서 observer_ 같이 리스트 형태로 관리하는 것이다. 그러나 방법은 때로 Observer 객체의 수가 적고 Subject 객체 여러 개가 이들을 참조하는 형태일 때에는 자료 공간의 낭비를 초래할 있다. 왜냐하면 Subject 객체가 Observer 객체에 대한 참조 값을 중복해서 가질 것이기 때문이다.
    이와 같은 경우 저장 공간의 낭비를 최소화시킬 있는 가지 방법은 Subject 객체와 Observer 객체간의 연관 관계를 별도의 테이블로 관리하면서 Subject 객체에 의해 요청이 있을 경우에만 해당 Subject 객체와 연관된 Observer 객체의 참조 리스트를 되돌려주는 방식이 있을 있다. 이런 방식을 적용할 경우 Subject 객체는 내부에 Observer 객체에 대한 참조 리스트를 저장할 필요가 없으며, Subject 객체 여러 개와 Observer 객체간의 관계도 테이블 하나로 관리될 있기 때문에 프로그램 전체적인 저장 공간 사용도 줄어들 있을 것이다. 다만 경우 문제가 있는 것은 Subject 객체가 변경된 정보를 Observer 객체들에게 반영하고자 다소 시간이 걸릴 있다는 점이다.
    다음으로 고려해볼 사항은 Subject 객체 여러 개가 동일한 Observer 객체에게 영향을 , 각각의 Subject 객체는 자신과 관련된 데이터 값만 변경하도록 Observer 객체에게 요청하려면 어떤 방법이 있겠는가 하는 문제다. 문제를 해결하기 위한 좋은 방법은 Observer클래스에 정의된 Update() 멤버 함수의 인자로 Subject 객체를 전달하는 것이다. 이렇게 Subject 객체가 인자로 전달되면 Observer 객체는 전달된 Subject 객체로부터 가져올 있는 데이터만 가져와서 변경된 내용을 반영하면 것이기 때문이다. 방식은 Observer 객체가 Subject 객체들로부터 데이터 값을 가져오기 위한 자료구조가 모두 동일한 형태여야 적용 가능할 것이다.
    다음으로 Observer 패턴 구현 고려해야 사항은 Subject 클래스의 Notify() 멤버 함수를 누가 부르도록 것인가 하는 문제다.
    가지 방법이 가능한데 하나는 Subject 객체의 데이터 값이 변경될 때마다 Notify() 멤버 함수가 자동으로 불리워지게 만드는 것이다. 방법은 정보 변경이 있었을 모든 Observer 객체에게 변경된 정보가 전달되었는지를 Client 일일이 신경쓸 필요가 없다는 장점이 있다. 반면 방식은 정보가 변경될 때마다 Notify() 멤버 함수가 호출되므로 비효율적일 있다.
    다른 방법으로는 Client 데이터 변경이 최종적으로 완료된 시점에 Notify() 멤버 함수를 불러 주는 방식이다. 방식의 장점은 데이터 값이 변경되고 있는 도중에 불필요하게 Notify() 멤버 함수가 불리지 않는다는 점이다. 따라서 데이터 변경이 효율적으로 이루어질 있다. 반면 방식은 Client 명확히 Notify() 멤버 함수를 부르도록 세심하게 신경을 써야 한다는 문제가 있을 있다.
    다른 고려사항은 Subject 객체가 소멸될 경우 어떻게 것인가 하는 점이다. 만약 Subject 객체가 소멸될 아무런 작업을 하지 않는다면, Observer 객체들은 소멸된 객체를 참조하는 문제를 일으킬 것이다. 반면 Subject 객체가 소멸될 자신을 참조하고 있는 모든 Observer 객체들을 같이 소멸시키는 것도 바람직하지 않다. 왜냐하면 다른 Subject 객체가 해당 Observer 객체들을 참조하고 있을 있기 때문이다. 따라서 가장 적절한 방법은 Subject 객체를 소멸하기 전에 자신을 참조하고 있는 Observer 객체들에게 자신이 소멸됨을 알려 적절한 조치를 취하게 하는 것이다.
    Observer패턴 구현 주의해야 것으로는 Subject객체의 Notify() 멤버 함수가 불리기 전에 반드시 Subject 객체 내부 상태 값은 일관성을 유지하게 해야 한다는 점이다. 만약 그렇지 않으면 Notify() 멤버 함수에 의해 Observer 객체들은 비일관된 데이터 값을 반영하는 문제가 발생할 것이다.
    같은 문제를 피하기 위한 가지 방법은 26장에서 언급할 Template Method 패턴을 활용하는 것이다. 방법은 Subject 클래스에 Template Method 해당하는 멤버 함수를 정의하되 내부를 다른 멤버 함수들을 불러주는 형태로 구현하는 것이다. 그런 다음 Notify() 멤버 함수는 모든 작업이 끝난 다음에 불러주게 하면, 데이터 값이 일관되게 변경된 다음에 Notify() 멤버가 불리워지므로, Observer 객체가 비일관된 데이터 값을 반영하는 일을 막을 있게 것이다.
    다음으로 고려해볼만한 사항은 Subject 객체와 Observer 객체간에 변경된 데이터 값을 전달하는 방법에 대한 것이다.
    먼저 생각해볼 있는 방법은 [소스 23-1]에서처럼 Subject 객체는 데이터 값이 변경되었다는 사실만 통보하고 Observer 객체가 변경된 데이터 값을 받아가는 방식이다. 방식의 경우에는 Subject 객체가 Notify() 멤버 함수 내에서 Observer 객체의 Update() 멤버 함수를 호출할 아무런 인자를 전달하지 않는 형태가 것이다. 대신 Observer 객체가 Subject 객체의 데이터 값을 가져가기 위한 함수를 호출하게 것이다. 이처럼 Observer 객체가 필요한 데이터를 가져오는 형태를 Pull 모델이라고 하는데, 이런 방식을 적용하게 되면 데이터 값이 변경되었다는 통보와 실제 변경된 데이터 값을 가져오는 동작이 구분되어 효율이 떨어질 있으나 Update() 멤버 함수를 항상 동일한 인터페이스 형태로 사용할 있다는 장점이 있다.
    이와 달리 Subject객체가 Observer 객체에게 데이터 값이 변경되었다는 사실을 통보하면서 변경된 데이터 값을 동시에 전달하는 방식이 있을 있다. 경우에는 Subject 객체가 Notify() 멤버 함수 내에서 Observer 객체의 Update() 멤버 함수를 호출할 변경된 데이터 값을 인자로 전달하는 형태를 취하게 것이다. 이런 방식을 Push 모델이라고 하는데, 같은 방식을 적용하게 되면 데이터 값이 변경되었다는 사실을 통보하면서 곧바로 변경된 데이터 값을 전달하기 때문에 좀더 효율적일 있으나, Update() 멤버 함수의 인터페이스가 항상 동일하지 않아 사용에 불편할 있다.
    Observer 패턴 구현 고려해야할 사항으로 어떻게 하면 보다 효율적으로 Observer 패턴을 동작하게 만들 것인가 하는 점이다.
    가지 가능한 방법은 Subject 객체 내의 데이터 값이 변경되더라도 모든 Observer 객체에게 데이터 값이 변경되었다는 사실을 통보하는 것이 아니라 변경된 데이터 항목과 관련된 Observer 객체에게만 데이터 값의 변경 사실을 통보하는 방식이다. 이렇게 하려면 먼저 Observer 객체를 Subject 객체에게 등록할 어떤 데이터 항목을 참조하는지도 같이 알려주어야 것인데, 이는 Subject 클래스의 Attach() 멤버 함수에 인자를 추가하는 방식으로 구현 가능할 것이다. 다만 방식을 적용하자면 Observer 객체가 Subject 객체 내부에서 관리하는 데이터 항목에 대해 미리 알고 있어야 한다는 전제가 있어야 한다.
    한편 여러 개의 Subject 객체와 Observer 객체가 존재할  이들간에 데이터 변경 사실을 통보하거나 변경된 데이터 값을 반영하는 것이 무지 복잡하고 비효율적일 있다. 이런 문제를 해결하기 위한 가지 방법은 Subject객체들과 Observer 객체들 사이에 ChangeManager 같은 별도의 객체를 두어 데이터 변경 사실을 통보하거나 변경된 데이터 값을 일괄적으로 반영하도록 관리해주는 것이다.


    • OBSERVER 패턴 정리
    Subject 객체와 Observer 객체는 통상적으로 1:N 관계를 형성한다. 또한 Subject 클래스는 자신의 객체에 의해 영향받을 Observer 객체들을 등록하거나 삭제할 있는 Attach() Detach() 멤버 함수를 제공하고 있다. 그리고 Subject 객체는 내부적으로 Observer 객체들을 저장, 관리하며 데이터 값에 변경이 있을 경우 Notify() 멤버 함수를 통해 내부적으로 관리하고 있던 모든 Observer 객체에게 데이터 값의 변경 사실을 통보하게 된다. 이때 데이터 값의 변경 사실을 통보받은 ConcreteObserver 객체는 ConcreteSubject 클래스에서 정의한 GetState() 멤버 함수를 통해 변경된 데이터 값을 받아오게 된다.


    Observer패턴이 유용한 경우
    • 하나의 객체가 가지 측면을 가지면서 어느 측면이 다른 측면에 의존적일 각각의 측면을 별도의 객체로 정의하고 Observer 패턴을 적용시키면 재사용성이 높아지고, 변경이 용이해진다.
    • 객체에 대한 변경이 다른 객체들에게도 영향을 미치는 상황에서 얼마나 많은 객체가 영향을 받는지를 일일이 관리하고 싶지 않을 유용하다.
    • 하나의 객체가 다른 객체에게 변경 사항을 알려주어야 하는데, 구체적으로 어떤 객체들에게 통보를 해야 할지 알지 못할 유용하다.
    Observer패턴의 장단점, 다른 패턴들과의 관계
    • Observer 패턴을 적용하게 되면 Subject 클래스와 Observer 클래스가 서로 독립적으로 변경될 있다는 장점이 있다.
    • Subject 객체는 통상 내부적으로 Observer 객체들을 리스트로 관리하게 되는데 이때 관리되는 Observer 객체의 구체적인 자료형은 Observer 클래스의 하위 클래스면 아무런 상관이 없다. 이는 Subject 클래스와 Observer 클래스간의 결합도가 낮다는 것을 의미하는데, 이로 인해 Subject 객체와 Observer 객체는 서로 다른 계층에 존재하면서도 쉽게 같이 맞물려 동작할 있게 된다.
    • Observer 패턴에서 Subject 객체는 변경된 사항이 있으면 모든 Observe 객체들에게 사실을 알리는 방송(Broadcasting) 하게 된다. 이는 다시 말해 Subject 객체가 개별 Observer 객체들을 특별히 구분하지 않는다는 것을 의미한다. 그렇기 때문에 Observer 객체의 입장에서는 Subject 객체에게 자기 자신을 등록하거나 삭제하는 것이 자유로워 있다.
    • Observer 객체들은 Subject 객체의 정보를 서로 서로 변경시킬 있는데 이로 인해 때로 동일한 변경 작업이 서로 다른 Observer 객체에 의해 반복 수행되는 사태가 벌어질 있다. 이러한 문제가 발생하는 원인은 Subject 객체와 Observer 객체가 데이터 값이 변경되었다는 사실만을 주고 받을 실제로 어떤 데이터 값이 어떻게 변경되었는지 명시적으로 교환하지 않기 때문이다.
    • 21장의 Mediator 패턴과 유사한 객체 구조를 가진다. 그러나 Observer 패턴은 객체들간의 의존 관계를 분산시키기 위한 목적으로 설계된 것인데 반해 Mediator 패턴은 각기 분산되어 복잡한 관계를 이루는 객체들을 중앙 집중 형태로 묶어주기 위해 설계된 것으로 사용 목적이 다르다는 차이가 있다.
    • Observer 패턴에서 Subject 객체는 통한 1개만 존재하므로 Singleton 패턴을 같이 사용해서 정의할 경우도 많이 있을 있다.
반응형