디자인패턴 - 옵저버패턴(Observer Pattern)

2019. 8. 13. 15:21프로그래밍언어/디자인패턴

 

어떤 클래스에 변화가 일어났을 때, 이를 감지하여 다른 클래스에 통보해주는 것이 observer 패턴이다. 그림을 보면 이 패턴은 외부 객체의 상태 변화에 따라(subject 클래스) observer 객체(observer 인터페이스 클래스)는 이에 상속되어 있는 다른 객체(concreteObserver)들에게 변화된 상태를 전달하고(notify 메서드) 상속된 객체들은 그에 맞게 기능을 수행하는 형태로 구성된다.

이 패턴에 해당되는 예는 관심 있는 어떤 블로그를 미리 등록해놓으면 그 블로그에 새 글이 올라올 때마다 자동으로 알려주는 것과 같다. 즉 observer 패턴은 어떤 일이 생기면 미리 등록한 객체들에게 상태 변화를 알려주는 역할을 한다.

 

다시 말하자면 Subject 객체의 상태에 변화가 있을 때, Observer들에게 상태변화를 알린다. 그 이후 Observer들은 상태변화에 맞게 행동을 취한다. 우리가 잘 알고 있는 신문사 구독과 비슷한 이론인 것이다. 어떤 고객이 신문사에 구독을 신청하면 신문사가 새로운 신문을 발행할때마다 고객에게 신문을 보내는 것과 같은 행동이다.

 

예를 들어 온도,습도,기압 등의 기상 상태를 센서로부터 수집하는 Subject가 있다고 생각하고, 해당 Subject는 기상 상태의 변화가 있을때, Observer들에게 변화를 알리고 Observer들은 기상 상태 변화에 따른 새로운 데이터를 디스플레이에 출력하는 것을 구현하는 상황이다.

 

여기서 적용할 수 있는 패턴이 옵저버 패턴이다. 바로 구현해보자.(예제는 단순히 온도 변화만 다루었다.) 그리고 구독을 해지하는 등의 코드도 구현하지 않았고 상태변화가 있을 때, 옵저버들에게 알려주는 기능만 구현하였다.

 

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
public class Subject extends Observable{
    
    private int temp;
    
    public void changeTemp(int temp) {
        setChanged();
        notifyObservers(temp);
    }
    
    public void changeTemp() {
        setChanged();
        notifyObservers();
    }
    
    public void setChangeTemp(int temp) {
        this.temp = temp;
        changeTemp(temp);
    }
 
    public int getTemp() {
        return temp;
    }
 
    public void setTemp(int temp) {
        this.temp = temp;
        changeTemp();
    }
    
}
 
cs

 

Subject 클래스이다. 이미 자바에는 옵저버 패턴의 구현을 쉽게 해주는 API를 제공하고 있다. Subject를 구현하기 위해서는 Observable 클래스를 상속받아 확장하여 사용하면 된다.(사실 Observable을 확장하면 단점들이 존재한다. 반드시 상속을 강요하고 있으며 추상클래스가 아닌 일반 클래스로 되어 있기에 나중에 필요한 기능을 확장하기 위하여 상속을 받을 수 없다. 왠만하면 새로 구현해서 사용하는 것도 추천한다.)

 

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
public class Observer1 implements Observer {
    
    private Observable sub;
    private int temp;
    
    public Observer1(Observable sub) {
        this.sub=sub;
        sub.addObserver(this);
    }
 
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof Subject) {
            if(arg != nullthis.temp=(int)arg;
            else this.temp=((Subject) o).getTemp();
        }
        
        display();
 
    }
    
    public void display() {
        System.out.println("Observer1 - "+temp);
    }
 
}
 
public class Observer2 implements Observer {
    
    private Observable sub;
    private int temp;
    
    public Observer2(Observable sub) {
        this.sub=sub;
        sub.addObserver(this);
    }
 
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof Subject) {
            if(arg != nullthis.temp=(int)arg;
            else this.temp=((Subject) o).getTemp();
        }
        display();
    }
 
    public void display() {
        System.out.println("Observer2 - "+temp);
    }
    
}
cs

 

Observer라는 인터페이스를 상속한 Observer 클래스 2개를 구현하였다. 해당 인터페이스에는 Subject에서 호출할 update 메소드만 오버라이드해서 구현해주면 된다. 옵저버 구현체들에서는 push 방식으로 데이터를 매개변수(arg)로 받아도 되지만 Subject 클래스에서 상태값에 대한 getter를 열어주어서 Observer 구현체들이 pull(getter호출)해서 가져가도 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ObserverMain {
 
    public static void main(String[] args) {
        
        Subject sub= new Subject();
        
        Observer observer1 = new Observer1(sub);
        Observer observer2 = new Observer2(sub);
        
        sub.changeTemp(20);
        sub.setTemp(10);
    }
 
}
 
=>결과
Observer2 - 20
Observer1 - 20
Observer2 - 10
Observer1 - 10
cs

 

결과적으로 Observer 구현체들의 어떠한 메소드도 호출하지 않았음에도 불구하고 Subject 클래스의 상태가 바뀔 때마다 Observer의 메소드가 호출되어 동작한다. 이러한 패턴이 옵저버 패턴이다. 이러한 패턴을 다른 말로 pub/sub 패턴이라고 부르기도 한다. 이러한 옵저버 패턴을 자바는 내부적으로 비동기처리 API의 내부 구현로직으로 사용하기도 한다.

 

위의 구현을 보면 Observer 구현체들의 인스턴스 변수로 Observable 인터페이스가 선언되어 있는 이유는 우선 생성자가 호출될때 해당 Observable 변수가 초기화되고, 이후에 별로 쓰이지 않는 듯 보이지만, 필자가 이야기 했듯 해당 예제에는 구독을 해지하는 등의 로직이 포함되어 있지않다. 추후에 구독을 해지하는 로직을 Observer 구현체들에게 메소드 형태로 넣어주고 내부적으로 Observable의 deleteObserver를 호출하게 하면 된다.