일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- Union-Find
- Factory method pattern
- flyweight pattern
- Set
- 자료구조
- Trie
- 분포 기반 정렬 알고리즘
- 명령패턴
- 디자인패턴
- 유니온-파인드
- 비교 기반 정렬 알고리즘
- 동적 계획법
- 트리
- 정렬 알고리즘
- 외적
- 생성패턴
- Abstract Factory pattern
- 팩토리패턴
- command pattern
- C++ STL 정리
- Unreal Collision
- BFS
- 경량 패턴
- 두 직선사이 교점
- Queue
- 관찰자(Observer) 패턴
- 스택
- 트리순회
- object channel
- 깊이 우선 탐색
- Today
- Total
KimMK
관찰자(Observer) 패턴 본문
디자인패턴 중 행위 패턴인 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에 알림이 가고 자동으로 내용이 갱신되는 방법으로 일대다의 의존성을 가진다. 상호작용하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인 패턴이다.
옵저버 패턴에는 Subject(주체)와 Observer(관찰자)라는 두 가지 개념이 사용되는데, Subject는 상태가 변할 수 있는 객체를 나타내고, Observer는 Subject의 상태에 관심이 있는 객체를 나타낸다.
Subject는 Observer의 리스트를 관리하며 Subject의 상태가 변하면 Observer들에게 알림을 보내어 각 Observer가 적절한 조치를 취할 수 있도록 한다.
옵저버 패턴은 MVC(Model-View-Controller) 아키텍처 패턴에서 주로 사용되며, GUI 프로그래밍이나 이벤트 기반 시스템에서 자주 활용된다. 예를 들면, 사용자 인터페이스에서 버튼이 클릭되었을 때, 클릭 이벤트를 처리하기 위해 옵저버 패턴을 사용할 수 있다.
게임에서 옵저버 패턴을 사용한다면, 업적 관리에 사용할 수 있다.
각 업적을 이벤트로 따로따로 관리한다면 관리가 너무 어려워진다. 만약, 몬스터 100마리를 죽이는 업적이라면 몬스터에 카운팅 변수를 넣고, 맵 개척 업적이라면 맵을 이동할 때마다 그것을 관리하는 쪽에 넣어줘야 한다. 이것이 너무 많아지면 관리하기가 까다롭다. 따라서 이것을 분산시키는 것이 아니라 옵저버 패턴을 이용해 통합해서 관리를 하는 것이다.
장점
- 느슨한 결합(Loose Coupling): 주체와 옵저버는 서로 독립적으로 변경될 수 있기 때문에, 즉 객체간의 결합도가 느슨하면서 시스템의 유연성과 확장성을 높이고 일관성 유지가 가능하다.
- 이벤트 기반 시스템 구현 용이성: 이벤트 발생시 옵저버들을 통해 이벤트 처리를 하기 때문에 이벤트 기반 시스템에서 사용하기 용이하다.
- 상태 변경 시 자동으로 알림 전달: 주체의 상태가 변경될 때마다 옵저버에게 자동으로 알림을 보내기 때문에, 주체와 옵저버간의 일관성을 유지하면서 상태 변경을 처리할 수 있다.
- 확장성: 주체와 옵저버 인터페이스를 구현하기만 하면 새로운 옵저버나 주체를 추가하기 용이하다.
단점
- 순서 보장: 옵저버들은 순서를 보장하지 않기 때문에 옵저버간의 순서에 따라 결과가 달라질 수 있다.
- 오버헤드: 옵저버가 많은 경우 주체의 상태 변경시마다 모든 옵저버에게 알림을 보내야 하기 때문에 오버헤드가 발생할 수 있다.
- 데이터 무결성 보장: 주체와 옵저버 사이의 데이터 무결성 보장이 필요한 경우, 옵저버들간의 상호작용을 관리하기 어려울 수 있다.
옵저버 패턴을 구현하기 위해서는 Subject와 Observer 인터페이스를 정의하고 Subject의 구현에서 Observer 리스트를 관리하며 Observer 구현에서는 Subject의 상태 변경에 대한 처리를 정의해야 한다.
이렇게 구현하면 Subject와 Observer는 서로 독립적으로 변경될 수 있고, 새로운 Observer나 Subject를 추가 및 제거하는 것도 용이해진다.
간단한 구현
// 옵저버(Observer) 클래스
class Observer {
public:
virtual void update() = 0; // 상태 변화 알림 메서드
};
// 주제(Subject) 클래스
class Subject {
private:
std::vector<Observer*> observers; // 옵저버 목록
int state; // 주제 객체의 상태
public:
void attach(Observer* observer) { // 옵저버 등록 메서드
observers.push_back(observer);
}
void setState(int state) { // 상태 설정 메서드
this->state = state;
notify(); // 상태 변화 알리기
}
int getState() { // 상태 조회 메서드
return state;
}
void notify() { // 상태 변화 알리기 메서드
for (auto observer : observers) {
observer->update();
}
}
};
옵저버 클래스와 주체 클래스를 정의하고 옵저버 클래스는 추상클래스로 옵저버들이 구현해야 할 update()메서드를 갖고 이를 통해 상태 변화를 알림
주체 클래스는 옵저버 리스트를 유지하며 상태 변화를 알리는 메서드들과 상태 값을 가짐
attach()메서드는 옵저버를 옵저버 목록에 추가하는 역할을 하고 setState()메서드는 상태 값을 설정 후 notify()메서드를 호출해 상태 변화를 알림
// 구체적인 옵저버 클래스
class ConcreteObserver : public Observer {
private:
Subject* subject; // 주제 객체
public:
ConcreteObserver(Subject* subject) {
this->subject = subject;
subject->attach(this); // 옵저버 등록
}
void update() override { // 상태 변화 알림 메서드 구현
std::cout << "Subject의 상태가 변화했습니다. 현재 상태: " << subject->getState() << std::endl;
}
};
ConcreteObserver 클래스는 구체적인 옵저버 클래스로 옵저버 클래스를 상속받아 update 메서드를 구현함
생성자에서 주체 객체를 인자로 받아 attach 메서드를 호출해 옵저버로 등록함
간단하게 테스트하기 위해 main을 구현하면,
int main() {
Subject subject;
ConcreteObserver observer1(&subject);
ConcreteObserver observer2(&subject);
subject.setState(1); // 상태 변화 알리기
return 0;
}
주체에서 상태를 1로 설정하면 옵저버들의 update 메서드가 호출되어 아래와 같은 출력이 발생하게 된다.
'C++ > 디자인 패턴' 카테고리의 다른 글
상태(State) 패턴 (0) | 2023.03.19 |
---|---|
싱글턴(Singleton) 패턴 (0) | 2023.03.12 |
경량(Flyweight) 패턴 (1) | 2023.03.11 |
디자인 패턴 (0) | 2023.03.11 |
명령(Command) 패턴 (0) | 2023.03.11 |