의도
- 객체 사이에 일 대 다의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 만듬
다른 이름
- 종속자(Dependent), 게시 - 구독 (Publish - Subscribe)
언제 쓰는가 ?
- 어떤 추상 개념이 두 가지 양상을 갖고 하나가 다른 하나에 종속적일 때. 각 양상을 별도의 객체로 캡슐화하여 이들 각각을 재사용할 수 있음.
- 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고, 프로그래머들은 얼마나 많은 객체들이 변경되어야 하는지 몰라도 될 때
- 어떤 객체가 다른 객체에 자신의 변화를 통보할 수 있는데, 그 변화에 관심 있어 하는 객체들이 누구인지에 대한 가정 없이도 그러한 통보가 될 때
구조
- Subject : 감시자들을 알고 잇는 주체. 임의 개수의 감시자 객체는 주체를 감시할 수 있음. 주체는 감시자 객체를 붙이거나 떼는 데 필요한 인터페이스를 제공함.
- Observer : 주체에 생긴 변화에 관심 있는 객체를 갱신하는 데 필요한 인터페이스를 정의함. 이로써 주체의 변경에 따라 변화되어야 하는 객체드르이 일관성을 유지함.
- ConcreteSubject : ConcreteObserver 객체에게 알려주어야 하는 상태를 저장함. 또한 이 상태가 변경될 때 감시자에게 변경을 통보함.
- ConcreteObserver : ConcreteSubject 객체에 대한 참조자를 관리함. 주체의 상태와 일관성을 유지해야 하는 상태를 저장함. 주체의 상태와 감시자의 상태를 일관되게 유지하는 데 사용하는 갱신 인터페이스를 구현함.
협력 방법
- ConcreteSubject는 Observer의 상태와 자신의 상태가 달라지는 변경이 발생할 때마다 감시자에게 통보함
- ConcreteSubject에서 변경이 통보된 후, ConcreteObserver는 필요한 정보를 주체에게 질의하여 얻어옴. ConcreteObserver는 이 정보를 이용해서 주체의 상태와 자신의 상태를 일치시킴.
결과
- Subject와 Observer 클래스 간에는 추상적인 결합도만이 존재함.
- 주체가 아는 것은 감시자의 리스트 뿐
- 브로드캐스트 방식의 교류를 가능하게 함.
- 주체가 보내는 통보는 구체적인 수신자를 지정할 필요가 없음
- 단지 자신의 감시자에게만 상태 변화 사실을 알려주면 감시자가 이 통보를 처리할지 무시할지를 결정함
- 예측하지 못한 정보를 갱신함.
- 감시자는 다른 감시자의 존재를 모름 -> 궁극적인 비용을 모름 -> 주체에 연속적인 연산이 일어나 계속 변경을 일으킬 수 있음
- 불필요한 갱신이 일어날 수 있고 추적도 까다로움
- 감시자가 무엇이 변했는지 알 수 있게 하는 별도의 프로토콜이 없는 한 변경을 유추하기 쉽지 않음
구현 / 고려 사항
- 주체와 그것의 감시자를 대응시킴
- 주체가 많고 감시자가 적다면 저장 공간 낭비 생김 -> 별도의 자료 구조(ex. 해시 테이블)를 두고 주체와 감시자 간의 대응 관계 관리 -> 별도의 방법을 통해 감시자의 참조를 얻는 부담이 생기기도 함
- 하나 이상의 주체를 감시
- 감시자가 여러 주체를 감시한다면 어떤 주체인지를 매개변수로 확인할 수도 있음
- 누가 갱신을 촉발(trigger)시킬 것인가?
- Subject의 상태를 변경하는 연산에서 Notify -> 사용자가 호출할 필요가 없는 장점이 있고, 계속되는 연산의 수행으로 여러 번 수정하는 단점이 있음
- 사용자가 적시에 Notify -> 갱신의 시작을 미루고 타이밍을 맞출 수 있음 -> 까먹을 수 있음(단점)
- 삭제한 주체에 대한 무효 참조자를 계속 유지할 때가 있음
- 주체의 삭제로 감시자들이 무효 참조자를 가지면 안됨 -> 주체는 감시자들에게 자신이 삭제되었으니 참조자를 없애라고 해야 함
- 통보 전에 주체의 상태가 자체 일관성을 갖추도록 만들어야 함
- 템플릿 메서드를 두어 일관성을 유지할 수 있음
- Notify는 템플릿 메서드를 통해 업데이트를 하여 일관성을 유지 후 변경의 제일 마지막 시점에 이루어지면 됨
- 감시자별 갱신 프로토콜을 피함
- 푸시 모델 : 주체가 자신의 변경에 대한 상세한 정보를 감시자에게 전달
- 풀 모델 : 주체가 최소한의 정보를 전달하고 감시자가 다시 상세 정보를 요청
- 자신이 관심 있는 변경이 무엇인지 명확하게 지정
- 감시자 등록 인터페이스를 만들 수 있음
- 복잡한 갱신의 의미 구조를 캡슐화
- 중재자를 두어 관리할 수 있음(또한 단일체가 유용)
- Subject와 Observer 클래스를 합침
예제 코드
- 책의 예제 그대로 구성해봤습니다. 사용자가 직접 Notify를 호출하게 끔 하여 구독 등록된 감시자들의 Update를 호출하는 방식입니다.
#include "Observer1.h"
int main()
{
Subject subject1("subject1");
Observer observer1("observer1");
Observer observer2("observer2");
Observer observer3("observer3");
Observer observer4("observer4");
subject1.Attach(&observer1);
subject1.Attach(&observer2);
subject1.Attach(&observer3);
subject1.Attach(&observer4);
subject1.Notify();
return 0;
}
- subject에 observer1,2,3,4가 attach하여 등록되게 되면 subject의 Notify 메서드를 통해 observer들의 Update 메서드가 호출되며 위와 같은 결과를 얻을 수 있습니다.
//Observer1.h
#pragma once
#include <list>
#include <string>
class Subject;
class Observer
{
public:
Observer(std::string name) : name(name) {}
virtual ~Observer();
virtual void Update(Subject* subject);
private:
std::string name;
};
class Subject
{
public:
Subject(std::string name) : name(name) {}
virtual ~Subject();
virtual void Attach(Observer* observer);
virtual void Detach(Observer* observer);
virtual void Notify();
std::string GetName() { return name; }
private:
std::list<Observer*> observers;
std::string name;
};
- observer들을 담는 컨테이너는 list만한 컨테이너가 없습니다. 삽입, 삭제가 간편하고 Notify 시 컨테이너 전체를 순차 탐색할 필요가 있기 때문입니다.
//Observer1.cpp
#include "Observer1.h"
#include <iostream>
Observer::~Observer()
{
}
void Observer::Update(Subject* subject)
{
std::cout << subject->GetName() << "로부터 업데이트 하라는 요청이 있습니다 ! " << std::endl;
std::cout << name << "을 업데이트 합니다 !" << std::endl;
}
Subject::~Subject()
{
}
void Subject::Attach(Observer* observer)
{
observers.push_back(observer);
}
void Subject::Detach(Observer* observer)
{
observers.remove(observer);
}
void Subject::Notify()
{
for (Observer* observer : observers)
{
observer->Update(this);
}
}
- list를 통해 observer 목록을 관리하고 Notify 시 observer들의 목록을 이용하여 순차 탐색하며 Update를 호출해줍니다.
'디자인 패턴 > 행위' 카테고리의 다른 글
전략(Strategy) (0) | 2024.01.15 |
---|---|
상태(State) (0) | 2024.01.12 |
메멘토(Memento) (1) | 2024.01.11 |
중재자(Mediator) (2) | 2024.01.11 |
반복자(Iterator) (1) | 2024.01.04 |