C, C++ 전체 링크
Architecture & Design Pattern 전체 링크
옵저버 패턴 (Observer Pattern) - 행동 패턴
- 객체 간의 일대다 종속성을 정의하는 패턴
- 객체의 상태가 변경될 때, 해당 객체에 종속된 다른 객체들이 자동으로 알림을 받아 갱신하도록 한다.
구현
- 주제(Subject) 객체는 옵저버들을 관리하고 상태의 변경을 감지하고, 옵저버들에게 알림을 보낸다.
- 옵저버(Observer) 객체는 주제의 상태 변경을 감지하고 처리한다.
- 주제와 옵저버 클래스를 추상화하여 각각의 기본 동작을 정의하고 구체적인 클래스를 구현한다.
장점
- OCP(개방-폐쇄 원칙), 기존 코드를 변경하지 않고 새로운 주제나 옵저버를 추가할 수 있다.
- 주제와 옵저버의 관계가 느슨해서 주제가 변경되어도 옵저버에 영향을 주지 않는다. (느슨한 결합, Loose Coupling)
- 런타임에 새로운 옵저버를 추가하거나 기존 옵저버를 제거할 수 있다. (확장성, Scalability)
- 주제와 옵저버는 독립적인 모듈로 구성된다. (Modularity)
- 주제는 옵저버가 특정 인터페이스를 구현하는 것만 알고, 옵저버를 추가한다고 해서 주제를 변경할 필요가 없다.
단점
- 주제 객체의 상태가 변경될 때마다 모든 옵저버에게 알림을 보내야 해서 처리 속도가 떨어질 수 있다.
- 주제와 옵저버 간의 순환 참조가 발생할 수 있다.
책을 판매하는 서점
서점에는 오프라인 서점, 온라인 서점(알라딘, Yes24 등)이 있다.
각 서점은 자율적으로 책을 원하는 가격에 판매할 수 있다.
여기서는 편의상 책을 한 권만 판다고 가정하자.
책 정보를 가지고 있는 BookData 클래스는 다음과 같다.
class BookData
{
public:
BookData() = default;
string getName() { return name; }
int getPrice() { return price; }
void setData(const string& newName, int newPrice)
{
name = newName;
price = newPrice;
}
private:
string name;
int price;
};
오프라인 서점은 원래 책의 가격(BookData - price)대로 판매하려고 한다.
알라딘은 10% 할인된 가격에 판매하고,
Yes24는 1000원 할인된 가격에 판매하려고 한다.
각 서점은 Store 를 상속받아 showPrice에서 원하는 가격을 보여주도록 하자.
class Store
{
public:
virtual void showPrice() = 0;
void update(BookData *data)
{
name = data->getName();
price = data->getPrice();
}
protected:
string name;
int price;
};
예를 들어 Yes24의 경우 아래와 같이 클래스를 만들 수 있다. (price - 1000)
class Yes24 : public Store
{
public:
void showPrice()
{
cout << "Yes24" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price - 1000 << endl << endl;
}
};
2만원짜리 해리포터 책을 각 서점에서 얼마에 판매하는지 다음과 같이 확인할 수 있다.
int main(void)
{
BookData* data = new BookData();
data->setData("harry-potter", 20000);
BookStore* bookStore = new BookStore();
Aladin* aladin = new Aladin();
Yes24* yes24 = new Yes24();
bookStore->update(data);
aladin->update(data);
yes24->update(data);
bookStore->showPrice();
aladin->showPrice();
yes24->showPrice();
return 0;
}
그런데 책의 가격이 3만원으로 변경되었다.
BookData의 setData를 이용해 가격을 변경하였지만,
각 서점은 data를 받아 update 하는 코드가 있어야 변경된 데이터에 대해 알 수 있다.
data.setData("harry-potter", 30000);
bookStore->update(&data);
aladin->update(&data);
yes24->update(&data);
bookStore->showPrice();
aladin->showPrice();
yes24->showPrice();
setData 이후 update를 하지 않으려면 BookData 클래스가 setData 이후 change 메서드를 호출하면 된다.
하지만 BookData가 각 서점의 클래스를 알아야하고, 새로운 서점이 추가되면 change 함수가 변경되어야 한다.
class BookData
{
public:
...
void setData(const string& newName, int newPrice)
{
...
change();
}
void change()
{
...
bookStore->update(&data);
aladin->update(&data);
yes24->update(&data);
...
}
};
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
using namespace std;
class BookData
{
public:
BookData() = default;
string getName() { return name; }
int getPrice() { return price; }
void setData(const string& newName, int newPrice)
{
name = newName;
price = newPrice;
}
private:
string name;
int price;
};
/* ----------------------------------------------------------------------------------- */
class Store
{
public:
virtual void showPrice() = 0;
void update(BookData *data)
{
name = data->getName();
price = data->getPrice();
}
protected:
string name;
int price;
};
class BookStore : public Store
{
public:
void showPrice()
{
cout << "BookStore" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price << endl << endl;
}
};
class Aladin : public Store
{
public:
void showPrice()
{
cout << "Aladin" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price * 0.9 << endl << endl;
}
};
class Yes24 : public Store
{
public:
void showPrice()
{
cout << "Yes24" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price - 1000 << endl << endl;
}
};
int main(void)
{
BookData* data = new BookData();
data->setData("harry-potter", 20000);
BookStore* bookStore = new BookStore();
Aladin* aladin = new Aladin();
Yes24* yes24 = new Yes24();
bookStore->update(data);
aladin->update(data);
yes24->update(data);
bookStore->showPrice();
aladin->showPrice();
yes24->showPrice();
cout << "update data" << endl << endl;
data->setData("harry-potter", 30000);
bookStore->update(data);
aladin->update(data);
yes24->update(data);
bookStore->showPrice();
aladin->showPrice();
yes24->showPrice();
return 0;
}
옵저버 패턴 (Observer Pattern)
옵저버는 상태 변경을 감지하고 처리하는 역할을 한다.
따라서 옵저버 인터페이스에는 update 메서드가 있으며, 각 옵저버는 update를 구현해야 한다.
class Observer {
public:
virtual ~Observer() = default;
virtual void update(string newName, int newPrice) = 0;
};
Subject 인터페이스는 옵저버를 관리하기 위한 메서드가 필요하다. (구독, 구독 해지, 알림)
class Subject
{
public:
Subject() = default;
virtual ~Subject() = default;
virtual void subscribe(Observer *o) = 0;
virtual void unsubscribe(Observer *o) = 0;
virtual void notify() = 0;
};
책의 이름과 가격 정보를 가지고 있는 BookData가 Subject 인터페이스를 상속받아 구현한다.
책의 정보가 setData에 의해 갱신되면 notify에서 모든 옵저버에게 알림을 보낸다.
class BookData : public Subject
{
public:
BookData() = default;
void subscribe(Observer *o) override { .. }
void unsubscribe(Observer *o) override { ... }
void notify() override {...}
void setData(const string& newName, int newPrice)
{
name = newName;
price = newPrice;
notify();
}
private:
string name;
int price;
list<Observer *> observers = {};
};
각 서점은 Observer를 상속 받아 update를 구현한다.
여기서는 update에서 showPrice에서 판매 가격을 보여주도록 한다.
서점이 생성될 때, BookData를 구독(subscribe)한다.
class Yes24 : public Observer
{
public:
Yes24() = default;
Yes24(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "Yes24" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price - 1000 << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
각 서점을 생성할 때, BookData를 구독하도록 한다.
그리고 data가 갱신되면 구독하고 있는 모든 서점이 알림을 받아 data를 update한다.
또한 런타임에 subscribe나 unsubscribe를 이용해서 서점을 구독 / 해지 할 수 있다.
int main(void)
{
BookData* bookData = new BookData();
BookStore* bookStore = new BookStore(bookData);
Aladin* aladin = new Aladin(bookData);
Yes24* yes24 = new Yes24(bookData);
bookData->setData("harry-potter", 20000);
cout << "unsubscribed yes24" << endl;
bookData->unsubscribe(yes24);
cout << "update data" << endl << endl;
bookData->setData("harry-potter", 30000);
return 0;
}
클래스 다이어그램으로 그려보면 다음과 같다.
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Observer {
public:
virtual ~Observer() = default;
virtual void update(string newName, int newPrice) = 0;
};
class Subject
{
public:
Subject() = default;
virtual ~Subject() = default;
virtual void subscribe(Observer *o) = 0;
virtual void unsubscribe(Observer *o) = 0;
virtual void notify() = 0;
};
class BookData : public Subject
{
public:
BookData() = default;
void subscribe(Observer *o) override { observers.push_back(o); }
void unsubscribe(Observer *o) override
{
if (!observers.empty())
{
delete o;
observers.remove(o);
}
}
void notify() override
{
for (auto o : observers)
o->update(name, price);
}
void setData(const string& newName, int newPrice)
{
name = newName;
price = newPrice;
notify();
}
private:
string name;
int price;
list<Observer *> observers = {};
};
/* ----------------------------------------------------------------------------------- */
class BookStore : public Observer
{
public:
BookStore() = default;
BookStore(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "BookStore" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
class Aladin : public Observer
{
public:
Aladin() = default;
Aladin(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "Aladin" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price * 0.9 << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
class Yes24 : public Observer
{
public:
Yes24() = default;
Yes24(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "Yes24" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price - 1000 << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
int main(void)
{
BookData* bookData = new BookData();
BookStore* bookStore = new BookStore(bookData);
Aladin* aladin = new Aladin(bookData);
Yes24* yes24 = new Yes24(bookData);
bookData->setData("harry-potter", 20000);
cout << "unsubscribed yes24" << endl;
bookData->unsubscribe(yes24);
cout << "update data" << endl << endl;
bookData->setData("harry-potter", 30000);
return 0;
}
구독 해지된 yes24를 제외하고 다른 서점의 책 정보가 잘 반영된 것을 알 수 있다.
notify 최적화
모든 서점은 BookData가 변경될 때마다 매번 알림을 받는다.
여기서 변경된 데이터가 특정 조건을 만족할 때만 알림을 보낼 수 있다.
만약에 책의 가격이 기존보다 떨어지는 경우라면 서점은 책 가격을 변경하지 않는다고 가정하자.
Subject 클래스는 상태 관리를 위한 변수와 메서드를 제공해야 한다.
class Subject
{
public:
...
protected:
void setChanged(bool value) { changed = value; }
bool getChanged() { return changed; }
private:
list<Observer *> observers;
bool changed = false;
};
새로운 가격이 더 큰 경우만 changed를 true로 변경하고, notify는 changed가 true인 경우만 실행하도록 구현한다.
void notify() override
{
if (getChanged() == false) return;
for (auto o : observers)
o->update(name, price);
setChanged(false);
}
void setData(const string& newName, int newPrice)
{
if (newPrice > price) setChanged(true);
name = newName;
price = newPrice;
notify();
}
해리포터 책의 가격이 20,000 → 10,000이 된 경우에는 알림이 발생하지 않는 것을 알 수 있다.
int main(void)
{
BookData bookData = BookData();
BookStore* bookStore = new BookStore(&bookData);
Aladin* aladin = new Aladin(&bookData);
Yes24* yes24 = new Yes24(&bookData);
bookData.setData("harry-potter", 20000);
cout << "unsubscribed yes24" << endl;
bookData.unsubscribe(yes24);
cout << "update data" << endl << endl;
bookData.setData("harry-potter", 10000);
return 0;
}
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Observer {
public:
virtual ~Observer() = default;
virtual void update(string newName, int newPrice) = 0;
};
class Subject
{
public:
Subject() = default;
virtual ~Subject() = default;
virtual void subscribe(Observer *o) = 0;
virtual void unsubscribe(Observer *o) = 0;
virtual void notify() = 0;
protected:
void setChanged(bool value) { changed = value; }
bool getChanged() { return changed; }
private:
list<Observer *> observers;
bool changed = false;
};
class BookData : public Subject
{
public:
BookData() = default;
void subscribe(Observer *o) override { observers.push_back(o); }
void unsubscribe(Observer *o) override
{
if (!observers.empty())
{
delete o;
observers.remove(o);
}
}
void notify() override
{
if (getChanged() == false) return;
for (auto o : observers)
o->update(name, price);
setChanged(false);
}
void setData(const string& newName, int newPrice)
{
if (newPrice > price) setChanged(true);
name = newName;
price = newPrice;
notify();
}
private:
string name;
int price;
list<Observer *> observers = {};
};
/* ----------------------------------------------------------------------------------- */
class BookStore : public Observer
{
public:
BookStore() = default;
BookStore(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "BookStore" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
class Aladin : public Observer
{
public:
Aladin() = default;
Aladin(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "Aladin" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price * 0.9 << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
class Yes24 : public Observer
{
public:
Yes24() = default;
Yes24(Subject* bd) : boodData(bd) { boodData->subscribe(this); }
void update(string newName, int newPrice) override
{
name = newName;
price = newPrice;
showPrice();
}
void showPrice()
{
cout << "Yes24" << endl;
cout << "Name : " << name << endl;
cout << "Price : " << price - 1000 << endl << endl;
}
private:
string name;
int price;
Subject* boodData = nullptr;
};
int main(void)
{
BookData* bookData = new BookData();
BookStore* bookStore = new BookStore(bookData);
Aladin* aladin = new Aladin(bookData);
Yes24* yes24 = new Yes24(bookData);
bookData->setData("harry-potter", 20000);
cout << "update data" << endl << endl;
bookData->setData("harry-potter", 10000);
return 0;
}
'개발 > Architecture & Design Pattern' 카테고리의 다른 글
C++ - 복합체, 컴포지트 패턴 (Composite Pattern) (1) | 2024.02.09 |
---|---|
C++ - 반복자, 이터레이터 패턴 (Iterator Pattern) (0) | 2024.02.04 |
C++ - 전략, 스트래티지 패턴 (Strategy Pattern) (0) | 2024.01.29 |
C++ - 싱글턴 패턴 (Singleton Pattern) (0) | 2024.01.26 |
아키텍처 & 디자인 패턴 용어 정리 (0) | 2024.01.26 |
댓글