[DesignPattern] 옵서버 패턴

옵서버 패턴


Observer 패턴에서는 관찰 대상의 상태가 변화하면 관찰자에게 알려준다.

Observer 패턴은 상태 변화에 따른 처리를 기술할 때 효과적이다.

옵서버 패턴은 데이터의 변경이 발생했을 경우 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용하다.

예를 들어 새로운 파일이 추가되거나 기존 파일이 삭제되었을 때 탐색기는 이를 즉시 표시할 필요가 있다. 탐색기를 복수 개 실행하는 상황이나 하나의 탐색기에서 파일 시스템을 변경했을 때는 다른 탐색기에게 즉각적으로 이 변경을 통보해야 한다. 다른 예로는 차량의 연로가 소진될 때까지의 주행 가능 거리를 출력하는 클래스, 연료량이 부족하면 경고 메시지를 보내는 클래스, 연료량이 부족하면 자동으로 근처 주유소를 표시하는 클래스 등에 연료량의 변화를 통보하는 경우가 있다. 이런 경우에 연료량 클래스는 연료량에 관심을 가지는 구체적인 클래스에 직접 의존하지 않는 방식으로 설계하는 것이 바람직하다.

--> 옵서버 패턴은 통보 대상 객체의 관리를 Subject 클래스와 Observer 인터페이스로 일반화한다. 그러면 데이터 변경을 통보하는 클래스(ConcreateSubject)는 통보 대상 클래스나 객체(ConcreateObserver)에 대한 의존성을 없앨 수 있다. 결과적으로 옵서버 패턴은 통보 대상 클래스나 대상 객체의 변경에도 ConcreateSubject 클래스를 수정없이 그대로 사용할 수 있도록 한다.



▶ 등장인물

 이름

 해설

 Observer

 관찰자를 나타내는 인터페이스

 NumberGenerator

 수를 생성하는 오브젝트를 나타내는 클래스

 RandomNumberGenerator

 랜덤으로 수를 생성하는 클래스

 DigitObserver

 숫자로 수를 표시하는 클래스

 GraphObserver

 간이 그래프로 수를 표시하는 클래스

 Main

 동작 테스트용 클래스


▶ 예제 프로그램 해설

Observer 패턴을 사용한 예제 프로그램을 살펴보자. 이번 예제 프로그램은 많은 수를 생성하는 오브젝트를 관찰자가 관찰해서 그 값을 표시하는 단순한 것이다.

단, 표시의 방법은 관찰자에 따라서 다르다. DigitObserver는 값을 숫자로 표시하지만, GraphObserver는 값을 간이 그래프로 표시한다.


1. Observer

- Observer(관찰자)의 역할: Observer는 Subject 역할로부터 '상태가 변했습니다.'라고 전달 받는 역할을 한다. 이를 위한 메서드는 update이다.

데이터의 변경을 통보 받는 인터페이스 즉, Subject에서는 Observer 인터페이스의 update 메서드를 호출함으로써 ConcreateSubject의 데이터 변경을 ConcreateObserver에게 통보한다.

package observer;


public interface Observer {

    public abstract void update(NumberGenerator generator);

}

Observer 인터페이스는 '관찰자'를 표현하는 인터페이스이고, 구체적인 관찰자는 이 인터페이스를 구현한다. 

update 메서드는 수를 생성하는 NumberGenerator 클래스에서 호출된다.(generator는 '생성하는 것', '발생장치'라는 의미이다.) update 메서드는 NumberGenerator 클래스가 '나의 내용이 갱신되었습니다. 표시 쪽도 갱신해 주십시오'라고 Observer에게 전달하기 위한 메서드이다.


2. NumberGenerator

- Subject(관찰 대상자)의 역할: Subject는 '관찰되는 대상'을 나타낸다. Subject 역할은 관찰자인 Observer 역할을 등록하는 메서드와 삭제하는 메서드를 가지고 있다. 또 '현재의 상태를 취득하는' 메서드도 선언되어 있다. ConcrerateObserver 객체를 관리하는 요소. Observer 인터페이스를 참조해서 ConcreateObserver를 관리하므로 ConcreateObserver의 변화에 독립적일 수 있다.

package observer;


import java.util.ArrayList;

import java.util.Iterator;


public abstract class NumberGenerator {

    private ArrayList observers = new ArrayList(); // Observer를 저장

    public void addObserver(Observer observer) { // Observer를 추가

        observers.add(observer);

    }

    public void deleteObserver(Observer observer) { // Observer를 삭제

        observers.remove(observer);

    }

    public void notifyObservers() { // Observer에 알림

        Iterator it = observers.iterator();      

        while (it.hasNext()) {                 

            Observer o = (Observer)it.next();  

            o.update(this);                   

        }                                     

    }                                      

    public abstract int getNumber(); // 수를 취득한다

    public abstract void execute(); // 수를 생성한다

}

NumberGenerator 클래스는 수를 생성하는 추상 클래스이다. 실제의 수의 생성(excute 메서드)과 수를 취득하는 부분(getNumber 메서드)은 하위 클래스에서 구현되도록 추상 메서드로 되어 있다.(subclass responsibilty). observers 필드는 NumberGenerator를 관찰하는 Observer를 보존하는 필드이다.

addObserver는 Observer를 추가하는 메서드이고, deleteObserver는 Observer를 삭제하는 메서드이다. notifyObservers 메서드는 Observer 전원에 대해서 '나의 내용이 갱신되었으므로, 당신의 표시를 갱신해 주십시오'라고 전한다. 이 메서드 안에서는 observer 안의 Observer들 한 사람 한 사함의 update 메서드를 호출하고 있다.


3. RandomNumberGenerator

- ConcreateSubject(구체적인 관찰 대상자)의 역할ConcreateSubject는 구체적으로 '관찰되는 대상'을 표현하는 역할이다. 상태가 변화하면 그것이 등록되어 있는 Observeer 역할에 전한다. 

변경 관리 대상이 되는 데이터가 있는 클래스. 데이터 변경을 위한 메서드인 excute가 있으며, excute에서는 자신의 데이터인 subjectObserver를 변경하고 Subject의 notifyObservers 메서드를 호출해서

ConcreateObserver 객체에 변경을 통보한다.

package observer;


import java.util.Random;


public class RandomNumberGenerator extends NumberGenerator {

    private Random random = new Random(); // 난수 발생기

    private int number; // 현재의 수

    public int getNumber() { // 수를 취득한다

        return number;

    }

    public void execute() {

        for (int i = 0; i < 20; i++) {

            number = random.nextInt(50);

            notifyObservers();           

        }

    }

}

RandomNumberGemerator 클래스는 NumberGenerator의 하위 클래스이고, 난수를 생성한다. 

execute 메서드는 난수(0~49의 범위의 정수)를 20개 생성하고, 그때마다 notifyObservers를 사용해서 관찰자에게 통지한다. 여기에서 사용되고 있는 nextInt 메서드는 java.util.Random 클래스의 메서드로 랜덤인 정수를 반환값으로 한다.


4. DigitObserver 

package observer;


public class DigitObserver implements Observer {

    public void update(NumberGenerator generator) {

        System.out.println("DigitObserver:" + generator.getNumber());

        try {

            Thread.sleep(100);

        } catch (InterruptedException e) {

        }

    }

}

DigitObserver 클래스는 Observer 인터페이스를 구현하는 클래스로 관찰한 수를 '숫자'로 표시하기 위한 것이다. update 메서드 안에서 인수로 주어진 NumberGenerator의 getNumber 메서드를 사용해서 수를 취득하고 그것을 표시한다.


5. GraphObserver

- ConcreateObserver(구체적인 관찰자)의 역할ConcreateObserver는 구체적인 Observer이다. update 메서드가 호출되면 그 메서드안에서 Subject 역할의 현재 상태를 취득한다.

ConcreateSubject의 변경을 통보받는 클래스. Observer 인터페이스의 update 메서드를 구현함으로써 변경을 통보받는다. 변경된 데이터는 ConcreateSubject의 getNumber 메서드를 호출함으로써 변경을 조회한다.

package observer;


public class GraphObserver implements Observer {

    public void update(NumberGenerator generator) {

        System.out.print("GraphObserver:");

        int count = generator.getNumber();

        for (int i = 0; i < count; i++) {

            System.out.print("*");

        }

        System.out.println("");

        try {

            Thread.sleep(100);

        } catch (InterruptedException e) {

        }

    }

}

GraphObserver 클래스도 Observer 인터페이스를 구현하는 클래스이다. 이 클래스는 관찰한 수를 '간이 그래프'로 표시한다.


6. Main

package observer;


public class Main {

    public static void main(String[] args) {

        NumberGenerator generator = new RandomNumberGenerator();

        Observer observer1 = new DigitObserver();

        Observer observer2 = new GraphObserver();

        generator.addObserver(observer1);

        generator.addObserver(observer2);

        generator.execute();

    }

}

실행결과)

DigitObserver:26

GraphObserver:**************************

DigitObserver:3

GraphObserver:***

DigitObserver:4

GraphObserver:****

DigitObserver:37

GraphObserver:*************************************

DigitObserver:32

GraphObserver:********************************

DigitObserver:26

GraphObserver:**************************

DigitObserver:22

GraphObserver:**********************

DigitObserver:2

GraphObserver:**

DigitObserver:15

GraphObserver:***************

DigitObserver:10

GraphObserver:**********

DigitObserver:19

GraphObserver:*******************

DigitObserver:29

GraphObserver:*****************************

DigitObserver:42

GraphObserver:******************************************

DigitObserver:20

GraphObserver:********************

DigitObserver:39

GraphObserver:***************************************

DigitObserver:1

GraphObserver:*

DigitObserver:18

GraphObserver:******************

DigitObserver:43

GraphObserver:*******************************************

DigitObserver:2

GraphObserver:**

DigitObserver:21

GraphObserver:*********************

Main 클래스에서는 RandomNumberGenerator의 인스턴스를 한 개 만들고, 그 관찰자를 두 개 만든다. observer1은 DigitObserver, observer2는 GraphObserver의 인스턴스이다.

addObserver 메서드를 사용해서 관찰자를 등록한 후 generator.excute를 사용해서 수를 생성한다.


cp.) 디자인 패턴의 목적 중의 하나는 클래스를 재이용 가능한 부품으로 만드는 일이다. Observer 패턴에서는 상태를 가지고 있는 ConcreateSubject 역할과 상태변화를 전달 받는 ConcreateObserver 역할이 등장했다.

그리고 이 두 가지의 역할을 연결하는 것이 인터페이스인 Subject 역할과 Observer 역할이다.

RandomNumberGenerator 클래스는 현재 자신을 관찰하고 있는 것이 (자신이 알려주는 상대가) DigitObserver 클래스의 인스턴스인지 GraphObserver 클래스의 인스턴스인지 몰라도 상관없다.

그러나 observer 필드에 저장되어 있는 인스턴스들이 Obsever 인터페이스를 구현하고 있다는 것은 알고 있다. 이 인스턴스들은 addObserver에서 추가 된 것이므로 반드시 Observer 인터페이스를 구현하고 있으며 update 메서드를 호출할 수 있다. 한편, DigitObserver 클래스는 자신이 관찰하고 있는 것이 RandomNumberGenerator 클래스의 인스턴스인지, 다른 XXXNumberGenerator 클래스의 인스턴스인지 신경 쓰지 않는다.

단지 NumberGenarator의 하위 클래스의 인스턴스이고, getNumber 메서드를 가지고 있다는 것은 알고 있다.


cf.) MVC

Model/View/Controller(MVC) 안의 Model과 View의 관계는 Observer 패턴의 Subject 역할과 Observer 역할의 관계에 대응한다. Model은 '표시 형식에 의존하지 않는 내부 모델'을 조작하는 부분이다.

또 View는 Model이 '어떻게 보일 것인지'를 관리하고 있는 부분이다. 일반적으로 하나의 Model에 복수의 View가 대응한다.