의도

  • 객체 사이에 일 대 다의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 만듬

 

다른 이름

  • 종속자(Dependent), 게시 - 구독 (Publish - Subscribe)

 

언제 쓰는가 ?

  • 어떤 추상 개념이 두 가지 양상을 갖고 하나가 다른 하나에 종속적일 때. 각 양상을 별도의 객체로 캡슐화하여 이들 각각을 재사용할 수 있음.
  • 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고, 프로그래머들은 얼마나 많은 객체들이 변경되어야 하는지 몰라도 될 때
  • 어떤 객체가 다른 객체에 자신의 변화를 통보할 수 있는데, 그 변화에 관심 있어 하는 객체들이 누구인지에 대한 가정 없이도 그러한 통보가 될 때

 

구조

GoF의 디자인 패턴 중 P.384

  • Subject : 감시자들을 알고 잇는 주체. 임의 개수의 감시자 객체는 주체를 감시할 수 있음. 주체는 감시자 객체를 붙이거나 떼는 데 필요한 인터페이스를 제공함.
  • Observer : 주체에 생긴 변화에 관심 있는 객체를 갱신하는 데 필요한 인터페이스를 정의함. 이로써 주체의 변경에 따라 변화되어야 하는 객체드르이 일관성을 유지함.
  • ConcreteSubject : ConcreteObserver 객체에게 알려주어야 하는 상태를 저장함. 또한 이 상태가 변경될 때 감시자에게 변경을 통보함.
  • ConcreteObserver : ConcreteSubject 객체에 대한 참조자를 관리함. 주체의 상태와 일관성을 유지해야 하는 상태를 저장함. 주체의 상태와 감시자의 상태를 일관되게 유지하는 데 사용하는 갱신 인터페이스를 구현함.

 

협력 방법

  • ConcreteSubject는 Observer의 상태와 자신의 상태가 달라지는 변경이 발생할 때마다 감시자에게 통보함
  • ConcreteSubject에서 변경이 통보된 후, ConcreteObserver는 필요한 정보를 주체에게 질의하여 얻어옴. ConcreteObserver는 이 정보를 이용해서 주체의 상태와 자신의 상태를 일치시킴.

 

결과

  1. Subject와 Observer 클래스 간에는 추상적인 결합도만이 존재함.
    1. 주체가 아는 것은 감시자의 리스트 뿐
  2. 브로드캐스트 방식의 교류를 가능하게 함.
    1. 주체가 보내는 통보는 구체적인 수신자를 지정할 필요가 없음
    2. 단지 자신의 감시자에게만 상태 변화 사실을 알려주면 감시자가 이 통보를 처리할지 무시할지를 결정함
  3. 예측하지 못한 정보를 갱신함.
    1. 감시자는 다른 감시자의 존재를 모름 -> 궁극적인 비용을 모름 -> 주체에 연속적인 연산이 일어나 계속 변경을 일으킬 수 있음
    2. 불필요한 갱신이 일어날 수 있고 추적도 까다로움
    3. 감시자가 무엇이 변했는지 알 수 있게 하는 별도의 프로토콜이 없는 한 변경을 유추하기 쉽지 않음

 

구현 / 고려 사항

  1. 주체와 그것의 감시자를 대응시킴
    1. 주체가 많고 감시자가 적다면 저장 공간 낭비 생김 -> 별도의 자료 구조(ex. 해시 테이블)를 두고 주체와 감시자 간의 대응 관계 관리 -> 별도의 방법을 통해 감시자의 참조를 얻는 부담이 생기기도 함
  2. 하나 이상의 주체를 감시
    1. 감시자가 여러 주체를 감시한다면 어떤 주체인지를 매개변수로 확인할 수도 있음
  3. 누가 갱신을 촉발(trigger)시킬 것인가?
    1. Subject의 상태를 변경하는 연산에서 Notify -> 사용자가 호출할 필요가 없는 장점이 있고, 계속되는 연산의 수행으로 여러 번 수정하는 단점이 있음
    2. 사용자가 적시에 Notify -> 갱신의 시작을 미루고 타이밍을 맞출 수 있음 -> 까먹을 수 있음(단점)
  4. 삭제한 주체에 대한 무효 참조자를 계속 유지할 때가 있음
    1. 주체의 삭제로 감시자들이 무효 참조자를 가지면 안됨 -> 주체는 감시자들에게 자신이 삭제되었으니 참조자를 없애라고 해야 함
  5. 통보 전에 주체의 상태가 자체 일관성을 갖추도록 만들어야 함
    1. 템플릿 메서드를 두어 일관성을 유지할 수 있음
    2. Notify는 템플릿 메서드를 통해 업데이트를 하여 일관성을 유지 후 변경의 제일 마지막 시점에 이루어지면 됨
  6. 감시자별 갱신 프로토콜을 피함
    1. 푸시 모델 : 주체가 자신의 변경에 대한 상세한 정보를 감시자에게 전달
    2. 풀 모델 : 주체가 최소한의 정보를 전달하고 감시자가 다시 상세 정보를 요청
  7. 자신이 관심 있는 변경이 무엇인지 명확하게 지정
    1. 감시자 등록 인터페이스를 만들 수 있음
  8. 복잡한 갱신의 의미 구조를 캡슐화
    1. 중재자를 두어 관리할 수 있음(또한 단일체가 유용)
  9. 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

+ Recent posts