본문 바로가기

PROGRAMMING/Design Pattern

[GoF] 관찰자 (Observer) 패턴

[GoF] 관찰자 (Observer) 패턴 행동 패턴

객체가 다른 객체의 상태 변화를 구독할 수 있게 하며, 구독한 객체의 상태가 갱신될 경우 객체에게 통지하여 갱신된 상태를 활용할 수 있도록 합니다.


 

요즈음 다양한 게임의 유저 인터페이스(UI)를 들여다보면, 게임 내부의 상태가 변함에 따라 유저 인터페이스도 자동으로 변하는 모습을 볼 수 있다. 예를 들어, 게임이 시작된다면 게임 진행을 위한 메뉴가 보여진다던가, 플레이어의 체력이 감소하면 HP 바가 덩달아 줄어드는 등. 이 두 개념은 서로 거리가 멀어 보이는데, 유저 인터페이스는 어떻게 상태 변화를 감지하고 대응하는것인가? 대부분의 경우 관찰자 패턴을 사용하여 구현할 수 있다.

관찰자 (Observer) 패턴은 관찰하고자 하는 객체에 상태가 변경 될 경우 메소드를 통해 통지받을 수 있도록 구독하는 기능을 지원한다. 다양한 관찰자가 대상을 구독하거나 해지할 수 있으며, 객체의 갱신에 대한 동적인 처리를 수행할 수 있도록 해 준다.

관찰자 패턴은 상태가 변할 수 있는 주체인 대상(Subject) 객체와 그 객체를 구독하여 통지받을 수 있도록 하는 관찰자(Observer) 객체로 구성된다. 대상 객체는 구독된 관찰자 객체 여러개를 참조하기에 서로 일대다(1 : N) 의 관계가 성립된다. 대상 객체와 관찰자 객체는 서로 추상화된 결합만 존재하기에 옵저버 패턴을 이용하면 관찰자 객체를 추가하거나 변경해도 대상 객체를 따로 변경할 필요가 없다. 그러므로, 상태를 활용하려는 객체와 그 상태를 가진 대상과의 결합을 완화시킬 수 있다.

대상 객체의 변경 시, 대상 객체가 관찰자 객체에게 갱신된 상태를 전달하는데 있어 관찰자 패턴은 두 가지 경우로 나누어진다: 대상 객체가 관찰자 객체에게 갱신된 상태를 메소드의 인자를 통해 전달하는 푸시 (Push) 방식과, 관찰자 객체가 대상 객체로부터 갱신된 상태를 직접 가져오는 풀 (Pull) 방식이 있다.

C#에서는 event 라는 키워드를 통해 관찰자 패턴을 자체적으로 이용할 수 있다. event 를 통해 상태의 갱신을 통지받으려는 경우에, 지정된 형식의 함수 포인터를 += 연산자를 통해 추가함으로써 구독의 기능을 수행할 수 있다. 또한, event 로의 구독을 해지하려는 경우 -= 연산자를 이용할 수 있다. event 가 클라이언트로부터 호출 될 경우, event 를 구독한 모든 함수 포인터가 따라서 호출된다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class PatternObserver
{
    public abstract class Subject<T>
    {
        private List<Observer<T>> _observers;
 
        public Subject()
        {
            _observers = new List<Observer<T>>();
        }
 
        public void AttachObserver(Observer<T> observer)
        {
            _observers.Add(observer);
        }
 
        public void Notify(T message)
        {
            foreach (Observer<T> observer in _observers)
            {
                observer.Update(message);
            }
        }
    }
 
    public class SubjectA : Subject<string>
    {
        public void SendMessage(string message)
        {
            Notify(message);
        }
    }
 
    public abstract class Observer<T>
    {
        public abstract void Update(T message);
    }
 
    public class ObserverA : Observer<string>
    {
        public override void Update(string message)
        {
            Console.WriteLine("Received the Message in ObserverA: " + message);
        }
    }
 
    public class ObserverB : Observer<string>
    {
        public override void Update(string message)
        {
            Console.WriteLine("Received the Message in ObserverB: " + message);
        }
    }
 
    public static void Main(string[] args)
    {
        SubjectA subject = new SubjectA();
 
        subject.AttachObserver(new ObserverA());
        subject.AttachObserver(new ObserverB());
 
        subject.SendMessage("Hello, World!");
    }
}
cs

1
2
3
Received the Message in ObserverA: Hello, World!
Received the Message in ObserverB: Hello, World!
Press any key to continue . . .
cs

위의 예제에서는 대상 객체와 관찰자 객체를 추상화하여 일반화된 상태를 통지할 수 있도록 하였다. 클라이언트는 ObserverA, ObserverB객체를 SubjectA객체에게 구독시킨 뒤 SubjectA객체의 SendMessage(T) 메소드를 통해 구독한 관찰자들에게 통지하였다. SubjectA 객체는 메소드의 인자를 통해 string의 메시지(상태)를 전달하였으므로 예제에서의 관찰자 패턴은 푸시 방식으로 구현되었다.