알아두면 쓸데있는 IT 잡학사전

1. 개요

어떤 클래스에 변화가 일어났을 때, 다른 클래스에 통보해 주는 패턴입니다. 통보를 하는 "어떤 클래스"가 Observable 이고, 통보를 받는 "다른 클래스"는 Observer입니다. Observable은 여러개의 Observer를 가질 수 있습니다. Observable이 "담임 떴다"를 외치면, Observer은 알아서 그에 걸맞는 행동을 합니다. 어떤 Observer는 만화책을 덮고 교과서를 꺼내고 어떤 Observer는 흘린 침 닦고 일어나서 공부하는 척하고 또 어떤 Observer는 먹던 도시락 치우고 창문 열어 환기를 시킵니다. Observable은 "담임 떴다"까지만 알려주지 "담임 떴으니깐, 누구는 어찌하고 누구는 저찌해라~" 까지 상세한 지시는 하지 않습니다.


2. 예제

//---------------- 변화를 통보하는 Observable -------------
package design.pattern.Observer;
import java.util.Observable;
public class Watcher extends Observable {
    public void action(String string) {
        System.out.println("======="+string+"========");
        setChanged();
        notifyObservers(string);
    }
}
//---------------- 변화를 통보받는 직원 -------------
package design.pattern.Observer;
import java.util.Observable;
import java.util.Observer;
public class Employee implements Observer {
    private String desc;
    public Employee(String desc) {
        this.desc = desc;
    }
    public void update(Observable o, Object arg) {
        if (o instanceof Watcher) {
            System.out.println(desc + "이 일하는 척");
        }
    }
    public String getDesc() {
        return desc;
    }
}
//---------------- 변화를 통보받는 사장 끄나풀 -------------
package design.pattern.Observer;
import java.util.Observable;
import java.util.Observer;
public class Spy implements Observer {
    private Employee employee;
    public Spy(Employee employee) {
        this.employee = employee;
    }
    public void update(Observable o, Object arg) {
        if (o instanceof Watcher) {
            System.out.println("고자질쟁이가 "+employee.getDesc() +"이 놀고 있었다고 고자질.");
        }
    }
}
//---------------- 테스트 클래스 -------------
package design.pattern.Observer;
public class Test {
    public static void main(String[] args) {
        Watcher watcher = new Watcher();
        Employee pc1 = new Employee("만화책보는 놈");
        Employee pc2 = new Employee("퍼질러 자는 놈");
        Employee pc3 = new Employee("포카치는 놈");
        //spy는 pc3을 보고 있음.
        //요놈은 꼰질르기의 대가
        Spy spy = new Spy(pc3);
        
        watcher.addObserver(pc1);
        watcher.addObserver(pc2);
        watcher.addObserver(pc3);
        watcher.addObserver(spy);
        
        watcher.action("사장 뜸.");
        watcher.deleteObserver(pc3);
        watcher.deleteObserver(spy);
        
        watcher.action("사장 뜸.");
    }
}

//---------------- 테스트 결과 -------------
=======사장 뜸.========
고자질쟁이가 포카치는 놈이 놀고 있었다고 고자질.
포카치는 놈이 일하는 척
퍼질러 자는 놈이 일하는 척
만화책보는 놈이 일하는 척
=======사장 뜸.========
퍼질러 자는 놈이 일하는 척
만화책보는 놈이 일하는 척

일단 테스트 클래스만 보세요.

사장이 오는 지 감시하는 Watcher가 하나 있습니다. 그리고 3명의 Employee와 사장 끄나풀인 1명의 Spy가 있습니다. 얘네들은 전부 Watcher한테 사장 뜨면 알려달라고 얘기해 놓습니다. addObserver() 가 Observer로 등록하는 부분입니다.

각각의 Employee 들은 사장이 뜨면, 하던 일 멈추고 일하는 척을 합니다. Spy는 사장이 뜨면, 누가 놀고 있었다고 꼰지릅니다. Employee와 Spy는 같은 통보(사장이 떴다는 것)을 받지만 하는 일은 전혀 다릅니다. 이렇게 같은 통보에 전혀 다른 Observer 들을  붙여 버릴 수도 있습니다.

테스트 코드와 테스트 결과를 보면, observer를 등록한 순서와 통보를 받는 순서가 일치하지 않는 것을 알 수 있습니다. 통보를 받는 순서는 등록 순서와 무관합니다. 

사장이 처음 떴을 때, "포카치는 놈"은 Spy한테 고자질 당해서 짤렸습니다. 짤렸기 때문에 더 이상 사장이 뜨던 말던 관심 없습니다. 그래서 Watcher에 Observer로 더 이상 등록되어 있을 필요가 없어졌습니다. 또, Spy는 잘했다고 포상휴가 갑니다. 얘도 더 이상 사장이 뜨는 지 안 뜨는지 통보 받을 필요가 없어졌습니다. 그래서 얘네 둘은 Watcher에 등록된 Observer 리스트에서 제거합니다. 색칠된 deleteObserver() 가 제거하는 부분입니다.

그래서 두 번째로 사장이 떴을 때는 "만화책 보는 놈"과 "퍼질러 자는 놈"만 통보를 받습니다.

다음은 Employee 클래스를 봅시다.

Observer 인터페이스를 구현하고 있습니다. Observer 인터페이스에는 update(Observable , Object) method가 정의되어 있습니다.

첫번째 인자 Observable은 update를 호출해준 Observable 을 말합니다. 여기서는 Watcher 클래스가 되겠습니다. 하나의 Observer는 여러 개의 Observable에 등록될 수가 있습니다. "만화책 보는 놈"의 경우는 사장이 떠도 통보를 받아야겠지만, 신간 만화책이 나왔을 때도 통보를 받아야 합니다. 각기 다른 통보인 만큼 할 일이 달라지겠지요. 그러나, update method를 각각의 Observable에 대응하도록 여러 개를 만들 수 없기 때문에 어떤 일인지를 Observable을 받아서 파악하고, 그에 걸맞는 행동을 합니다. 예제에서는 그 Observable이 단지 Watcher 클래스의 인스턴스인지 체크하는 방식으로 구현했습니다.

두번째 인자 Object를 통해서는 구체적인 정보를 받을 수 있습니다.  

이번에는 Watcher 클래스를 봅시다.

일단 Observable이란는 클래스를 상속받습니다. Observable 에는 몇 가지 method가 있습니다만 여기서는 두가지만 썼습니다. setChanged()와 notifyObservers() 입니다. setChanged() 는 변화가 일어났다는 것을 알리는 겁니다. 변화가 일어나지 않으면, 굳이 Observer 들에게 알릴 필요가 없습니다. 예를들어, Watcher는 무조건 감시하고 있는 클래스이기 때문에 잡상인이 뜨던가 사장이 뜨던가 다 압니다. 하지만, 잡상인이 떴을 경우는 Observer들에게 알릴 필요가 없습니다. 사장이 떴을 때만 setChanged()를 호출하고, Observer들에게 알립니다. (사장인지 아닌 지를 판단하는 로직은 코드가 길어져서 뺐습니다.) setChanged()가 호출되지 않고 notifyObservers()가 호출되면, 아무일도 일어나지 않습니다.

다음에 notifyObservers() method가 있는데, 이 메쏘는 오버로드되어있습니다. notifyObservers()와 notifyObservers(Object) 두 가지가 있습니다. Object에는 어떤 일이 일어났는지 상세 정보를 담을 수 있습니다. 사장이 떴는지, 부장이 떴는 지 정도의 정보를 담을 수 있겠지요. notifyObservers()는 notifyObservers(null) 과 같습니다. 

어찌되었건 notifyObservers(Object)가 호출이 되면, Observer들에게 전부 알립니다. 그러면, Observer들은 각각 자기가 가진 update() method가 호출됩니다.


3. Observer 의 특징

말이 Observer이지 단지 Observerable에게 통보를 받는 입장입니다. 

Observer 들은 Observable에 추가 삭제가 자유롭습니다. Observable 입장에서는 어떤 Observer인지 신경쓰지 않습니다. 단지 어떤 일이 일어났다는 통보만을 합니다. 통보에 대한 반응은 전적으로 Observer가 합니다. update() method가 Observer에 있고, notifyObservers() method가 Observable에 있는 것이 바로 그런 얘기 입니다.


4. JAVA API에 있는 Observer

또, GUI 쪽 얘기를 꺼내게 되는군요. GUI에 있는(awt, swing, swt 등등 전부 비슷합니다.) 각종 버튼, 텍스트 등에 add머시기Listener method 들이 잔뜩 있습니다. 이벤트가 일어나면, 뭔가를 호출하라는 것이지요. 예제에서 설명한 것과는 인터페이스가 다소 상이하긴 합니다.

GUI의 event는 Observer 패턴으로 구현되어 있어 이벤트 추가 삭제가 매우 자유롭습니다. 

'개발 > JAVA' 카테고리의 다른 글

[Design Pattern]Flyweight  (0) 2018.07.16
[Design Pattern]Prototype  (0) 2018.07.16
[Design Pattern]Facade  (0) 2018.07.16
[Design Pattern]Composite  (0) 2018.07.16
[Design Pattern]Composite  (0) 2018.07.16