옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로, 일대다(one-to-many) 의존성을 정의함.
일대다 관계는 주제(Subject)와 옵저버(Observer)에 의해 정의된다.
옵저버 패턴의 구현 방법은 여러가지가 존재하나, 대부분 주제(subject) 인터페이스와 관찰자(observer) 인터페이스가 들어있는 클래스 디자인으로 함.
주제에서는 관찰자를 등록 및 해지, 각 관찰자에 상태 변경을 알리기 위한 메서드를 포함하고 있음
주제와 옵저버가 서로 상호작용을 하긴 하지만 서로에 대해 잘 모른다는 것을 의미. 디자인 원칙상 서로 상호작용을 하는 객체 사이에는 가능한 느슨하게 결합하는 디자인을 사용해야 함 -> 객체 사이 상호의존성 최소화
주제가 옵저버에 대해서 아는것은 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 것 뿐이다.
옵저버는 언제든지 새로 추가할 수 있다.
새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경할 필요가 없다.
주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지 않는다.
https://en.wikipedia.org/wiki/Observer_pattern
- Subject: Observer 를 알고 있는 주체입니다. 이는 Observer 를 등록하고 제거하는 데 필요한 인터페이스를 정의합니다. 데이터를 관리하고, 데이터가 달라지면 관찰자에게 데이터 전달 (e.g. 신문 출판사)
- Observer: Subject 에서 변화했다고 알렸을때 갱신해야하는데 필요한 인터페이스를 정의합니다. (e.g. 구독자)
- ConcreteSubject: 객체에게 (Observer 들에게) 알려줘야할 상태를 저장하고, notify 해야할 함수를 만들도록 합니다.
- ConcreteObserver: noti 를 받았을때 행동할 로직을 작성합니다.
Model/View/Controller(MVC) 사용자 인터페이스 프레임워크
- MVC model = subject 역할
- MVC view = observer 역할
Android의 Event Listener
node.js의 event loop
browser의 Event Handler
e.g. 버튼 클릭에 event가 달리고, 버튼이 클릭될 때 마다 특정 행동을 하도록 observer에게 알리는 패턴
유명인이 트위터에 글을 쓰면 뉴욕타임즈, 가디언, 르몽드가 관심 키워드에 따라 반응하는 방식
interface Observer {
void notify(String tweet);
}
/* 삭제 예정 */
class NYTimes implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
}
}
/* 삭제 예정 */
class Guardian implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet more news in London... " + tweet);
}
}
}
/* 삭제 예정 */
class LeMonde implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine, and news! " + tweet);
}
}
}
interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o) {
this.observers.add(o);
}
public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}
사용 :
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers( "The queen said her favourite book is Java 8 in Action!");
그런데 Observer가 한 개의 메소드만 갖고 있는 인터페이스이므로, 람다를 사용하면 클래스를 만들지 않고도 옵저버를 등록할 수 있다.
f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
});
즉, NYTimes 클래스를 삭제하고 실행 코드를 다음과 같이 수정할 수 있다.
Feed f = new Feed();
// NYTimes를 대체하는 코드. 이제 NYTimes 클래스는 삭제해도 된다.
f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("money")) {
System.out.println(
"Breaking news in NY! " + tweet
);
}
});
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers( "The queen said her favourite book is Java 8 in Action!");
f.notifyObservers( "money!");