가게에서 새로운 상품이 출시되었을 때 (상태 변화가 일어날 때), 이를 사용자에게 알리고 싶은 경우가 생긴다. 이 경우 새로운 상품 출시에 따라(상태변화에 따라) 사용자에게 정보를 제공해야할 것이다.
또한 이 경우에 모든 사용자에게 이를 알리게 되면, 원치 않는 사용자는 당연하게 스팸처럼 여기게 될 것이다. 따라서 결국 원하는 대상에게만 이 사실을 알려야 되는 경우가 생긴다.
관찰 대상(Subject)의 상태가 변화하면 (이벤트가 발생하면), 이를 관찰자(의존적인 다른 객체)에게 알리는 패턴이다. 이 패턴은 상태 변화(이벤트가 발생)에 따른 처리를 기술할 때 효과적이다.
이름 | 설명 |
---|---|
Observer | 관찰자를 나타내는 인터페이스 |
NumberGenerator | 수를 생성하는 객체를 나타내는 추상 클래스, 관찰받는 대상을 의미한다. (Subject) |
RandomNumberGenerator | 랜덤하게 수를 생성하는 클래스 |
DigitObserver | 숫자로 수를 표현하는 클래스 |
GraphObserver | 간이 그래프로 수를 표현하는 클래스 |
Main | 동작 테스트 용 클래스 |
public interface Observer {
public abstract void update(NumberGenerator generator);
}
public abstract class NumberGenerator {
// Observer를 저장한다
private List<Observer> observers = new ArrayList<>();
// Observer를 추가한다
public void addObserver(Observer observer) {
observers.add(observer);
}
// Observer를 제거한다
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
// Observer에 통지한다
public void notifyObservers() {
for (Observer o: observers) {
o.update(this);
}
}
// 수를 취득한다
public abstract int getNumber();
// 수를 생성한다
public abstract void execute();
}
public abstract class NumberGenerator {
// Observer를 저장한다
private List<Observer> observers = new ArrayList<>();
// Observer를 추가한다
public void addObserver(Observer observer) {
observers.add(observer);
}
// Observer를 제거한다
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
// Observer에 통지한다
public void notifyObservers() {
for (Observer o: observers) {
o.update(this);
}
}
// 수를 취득한다
public abstract int getNumber();
// 수를 생성한다
public abstract void execute();
}
public class DigitObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
public class GraphObserver implements Observer {
@Override
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) {
}
}
}
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:2
GraphObserver:**
DigitObserver:24
GraphObserver:************************
... 중략
*/
추가적으로, 위 코드에서
Observer observer3 = new DigitObserver();
Observer observer4 = new DigitObserver();
generator.addObserver(observer3);
이렇게 추가를 한다고 생각해보자. 이 경우 observer4의 경우는 등록하지 않고, observer3의 경우는 등록을 하였다. 따라서 observer3는 notifyObservers()라는 이벤트가 발생하였을 때 관찰을 받고 있기 때문에 통지를 받으나, observer4의 경우는 통지를 받지 않게 된다.
Subject(관찰 대상자) 역할 : 관찰 대상을 나타내고, 현재 상태를 가져오는 메소드를 선언하고 있고, Observer를 등록하는 메소드가 삭제하는 메소드를 가진다.
ConcreteSubject(구체적인 관찰 대상자) 역할 : 구체적으로 관찰되는 대상을 표현하고, 상태가 변경되면 Observer에게 알린다. 또한 변화를 전달하기 위한 update 메소드를 호출한다.
Observer(관찰자)의 역할 : Subject로 부터 상태가 변화했음을 전달받는 대상이다(상태를 관찰한다). 이 프로그램에서는 Observer 인터페이스가 이 역할이다.
ConcreteObserver(구체적인 관찰자)의 역할 : 구체적인 Observer이며, update 메소드가 호출되면, 그 메소드 안에서 Subject의 상태를 취득한다. 프로그램에서는 DigitObserver와 GraphObserver 클래스가 이 역할을 맡는다.
RandomNumberGenerator 클래스는 자신이 현재 관찰 당하는 것이 구현체가 무엇인지에 대해서는 알지 않아도 되고, 다만 Observer 인터페이스만을 구현하면 된다는 사실만 알면된다. 또한 DigitObserver 클래스는 자신이 관찰하는 대상이 어떤 구현체의 인터페이스인지 알지 몰라도 된다.
이를 통해 구체 클래스로부터 추상 메소드를 분리하고, 인수로 인스턴스를 넘길 때, 추상 클래스를 넘겨 확장성에 유리해 질 수 있는 것이다.
사실상 observer는 '관찰자'라는 의미를 가지고 있지만, 이 역할은 관찰 당한다기보다는 알려 주는 것을 수동적으로 기다린다. 따라서 Publish-Subscribe 패턴이라고 불리기도 한다.
위 코드에서는 Observer를 구독자(사람)이라고 생각하고, NumberGenerator의 메소드인 notifyObservers를 Publish, 이벤트 발행이다.
참조 :
Java 언어로 배우는 디자인 패턴 입문