[디자인패턴] Observer Pattern
디자인 패턴은 공부할 수록 흥미롭다. 이전 프로젝트 개발을 할 때 찝찝했던 부분이 있었는데 그런 부분을 해결할 방법을 알 수 있어서 재미있다. 디자인 패턴에 대한 이해가 왜 필요한지 알고 공부를 하니 더 이해가 잘 된다. 이번에 공부한 옵져버 패턴은 특히 그렇다. 작년에 개발했던 장비 테스터 프로그램이 생각난다. PC가 장비에 연결된 시리얼 포트로 장비의 정보를 수신받아 화면에 출력하는 기능이 있었다. 내가 구현했던 방식은 데이터가 업데이트 되는 부분에서 화면 갱신을 하는 것이었다. 쓰레드를 돌려서 현재 들어온 값이 이전 값과 다르면 화면을 갱신하여 표시했다.
private void UpdateLine1Data()
{
while (flag == true)
{
if (GetRcvflag() == true)
{
display_update(Line1_Data);
}
SetRcvflag(false);
Thread.Sleep(500);
}
}
시리얼 통신 객체에서 데이터가 변하면 GetRcvflag()를 변경해 주었고 화면 업데이트는 이 쓰레드에서 수행했다. 지금 다시 코드를 보니 정말 이상하게 코드를 짰다. 문제는 갱신해야하는 부분이 Line1 뿐만 아니라 Line2, Line3 ... 등 여러개라서 각 부분마다 쓰레드를 추가해서 구현했다. 다시보니 정말 부끄러운 코드이다. 쓰레드를 남용하고 유지보수하기도 어렵다. 한 객체의 상태가 바뀌는 것을 알려줄 수 있는 것이 옵저버 패턴이다.
x
//
struct DATA{};
class Obserable;
class Observer{
public:
virtual void update(Obserable& obserable, DATA* data) = 0;
};
class Obserable{
std::vector<Observer*> observers;
public:
void addObserver(Observer* obs){//Observer는 순수 가상클래스라 객체생성이 불가능하다.
observers.push_back(obs);
}
void delObserver(Observer* obs){
for (size_t i = 0; i < observers.size(); i++)
if(observers[i] == obs)
observers.erase(observers.begin() + i);
}
void notifyObservers(DATA& data){ //public DATA를 해줘서 void*가 아닌 참조로 가져올 수 있게 만든다.
for (auto iter : observers)
iter->update(*this, &data);
}
};
크게 Observer
클래스와 Obserable
클래스가 있다. Obserable
클래스가 데이터 변경을 Observer
클래스에 알려주는 형식이다. 데이터 변경을 알려주는 클래스는 Obserable
을 상속받아서 데이터 갱신이 일어나는 시점에 notifyObservers()
를 호출하여 구독자에게 알린다. 구독자 클래스는 Observer
를 상속받아 update
가상 함수를 재정의 하여 데이터 갱신 시 처리할 내용을 작성하면 된다. Java의 경우 Java.util에 제공되는 Observer와 Obserable을 사용할 수 있지만 다중상속이 제한되기 때문에 제약이 발생한다. 하지만 Cpp는 상속 제한이 없기 때문에 더 유연한 사용이 가능할 것이다. 이번 패턴 템플릿을 개발하면서 상속에 대한 개념을 다시 잡을 수 있었다.
update()
의 data 부분을 템플릿으로 표현하도록 하여? 아니면 다른 방법으로 표시할 방법을 생각해봐야겠다.
Obserable
클래스의 observers를 어떤 자료구조로 구현해야하는지 잘 모르겠다.delObserver
를 이렇게 구현해도 되는지 모르겠다- 갱신하는 데이터 타입을 좀 더 유연하게 만들 수 없을까?
- 상속 받아서 사용하는 클래스인데 좀 더 편리하게 사용할 수 있는 방법은 없을까?
개선의 여지는 많지만 C++공부를 더 해야 고칠 수 있을 것 같다.
예시를 작성해 보았다. Server 클래스에서 데이터를 갱신하면 App과 Device에 알리는 구조를 만들고자 한다. 서버에서 Weather의 데이터가 갱신되면 App과 Device에 알려줄 것이다.
xxxxxxxxxx
CREATE_DATA(Weather){ //전송 데이터 타입 정의
double temperature;
int violet_idx;
};
class Server : public Obserable{ //Obserable 객체를 상속받아 배포기능을 넣는다.
public:
Weather data; //전송할 데이터 타입
void DataUpdated(){ //데이터가 갱신 되었을 때 호출할 함수
//데이터 갱신 시 notifyObservers를 호출하여 구독자들에게 알린다.
this->notifyObservers(this->data);
}
void SetData(double t, int v){ //데이터 갱신 함수
this->data.temperature = t;
this->data.violet_idx = v;
this->DataUpdated(); //데이터가 변경된 것을 알린다.
}
};
먼저 갱신할 데이터 타입인 Weather를 정의하였다. Server 클래스는 Obserable
을 상속받았으며 Weather 데이터를 가지고 있다. 데이터 갱신이 되면 notifyObservers
를 호출하여 구독자들에게 데이터가 변경 되었음을 알린다.
xxxxxxxxxx
class App : public Observer{
Obserable* subject;
public:
App(Obserable& obs){
this->subject = &obs;
this->subject->addObserver(this); //구독 등록
}
void update(Obserable& obserable, DATA* data){
GET_DATA(Weather, d);
std::cout << "App data(violet) : " << d.violet_idx << std::endl;
}
void del(){
this->subject->delObserver(this); //구독 삭제
}
};
Observer
클래스를 상속받은 App 구독자 클래스이다. 생성자에서 구독할 내용을 등록해 주었다. update
함수를 재정의하여 처리할 내용을 작성한다. Server에서 notifyObservers
가 호출되면 여기의 update
함수가 실행된다. 구독을 해지하려면 delObserver를 호출한다.
xint main(){
Server s; //서버 객체 생성
App a(s); //서버에서 데이터 수신하는 App 객체
Device d(s); //서버에서 데이터 수신하는 Device 객체
//서버에서 데이터를 갱신해준다.
s.SetData(24.3, 11);
s.SetData(26.3, 10);
std::cout << ">> App 구독 삭제" << std::endl;
a.del();
s.SetData(21.1, 9);
return 0;
}
xxxxxxxxxx
//output
App data(violet) : 11
Device data(temperature) : 24.3
App data(violet) : 10
Device data(temperature) : 26.3
>> App 구독 삭제
Device data(temperature) : 21.1
동작이 잘 되는 것을 확인할 수 있다.