옵저버 패턴 Observer Pattern
옵저버 패턴
- 다수의 객체(observer)가 특정 객체(subject)의 상태 변화를 감지하고 알림을 받는 패턴.
- 옵저버가 서브젝트로부터 데이터를 가져(pull)오지 않는다. 서브젝트가 데이터를 변경하고 그 데이터를 옵저버에 보낸다(push).
- subject와 observer 간 관계가 느슨하다.
- observer는 subject가 어떤 데이터를 생산하고 전파하는지 알지 못한다. 그저 데이터를 주고 받는 관계만 형성한다.
- 새로운 observer가 추가되더라도 subject는 영향을 받지 않는다.
- 반복되는 호출로 인한 리소스를 최소화 한다. 옵저버는 서브젝트의 상태가 변경되었는지 주기적으로 호출하지 않아도 된다.
- 런타임 시점에서 옵저버를 추가하거나 제거할 수 있다.
- 다만, observer 객체를 등록한 후 해지 않는다면 memory leak이 발생할 수도 있다
- 서브젝트의 코드가 복잡해질 경우, 옵저버 패턴을 포기하고 push가 아닌 pull로의 변경을 고려한다.
옵저버 패턴의 형태
- 옵저버는 아래처럼 서브젝트로부터 데이터를 push 받는 메서드를 가진다.
public interface Observer {
void sendMessage(String message);
}
- 옵저버에게 전달하는 서브젝트를 다음과 같이 정의하였다. 필요할 경우 서브젝트를 interface로 구현할 수 있다.
- 서브젝트는 옵저버에 문자열을 보낸다. 하지만 그 데이터가 어떤 종류인지 어떤 목표를 가지는지 옵저버는 알지 못한다. 유연한 관계를 유지한다.
import java.util.ArrayList;
import java.util.List;
public class ChatServer {
private final List<Observer> observers = new ArrayList<>();
public void register(Observer observer) {
observers.add(observer);
}
public void unregister(Observer observer) {
observers.remove(observer);
}
public void sendMessage(String message) {
observers.forEach(o -> o.sendMessage(message));
}
}
- main 메서드에서 아래와 같이 동작한다.
ChatServer chatServer = new ChatServer();
Observer kim = new User("kim");
Observer lee = new User("lee");
chatServer.register(kim);
chatServer.register(lee);
chatServer.sendMessage("hello!");
chatServer.unregister(lee);
chatServer.sendMessage("lee unregistered!");
메모리 해제의 문제
- 가비지 컬렉터(GC)는 모든 참조가 사라진 객체에 대해서 동작한다.
- 모든 옵저버가 (위의 코드를 기준으로) unregister되지 않을 경우, 서브젝트가 더 이상 동작하지 않더라도 옵저버는 제거되지 않는다.
- WeakReference 자료구조로 서브젝트가 옵저버를 보관할 수 있다. 하지만 항상 기대하는대로 동작하지 않을 수 있다.
- 옵저버를 명시적으로 해지하는 방법을 최우선으로 한다.
자바의 Observer api의 deprecated와 Flow api
- 자바의 옵저버 패턴을 위한 Observer는 deprecated 되었다.
- 사용에 있어서 몇 가지 불편한 점이 있다.
- setChange()를 해야만 notifyObservers()를 사용할 수 있다.
- notifyObservers() 시 옵저버의 호출 순서를 조정할 수 없다.
- flow api
- 자바 9 이후 사용 가능하다.
- 비동기 처리가 가능하다.
- 요청이 과도하여 발생하는 백프레셔(back pressure)를 클라이언트 차원에서 조절 가능하다.
스프링의 ApplicationEventPublisher
- 스프링이 제공하는 옵저버
- 어너테이션 기반으로 동작. 스프링이 지향하는 비침투적인 방식으로 구현되어 있다.
참조
- 백기선, 디자인 패턴 강의(https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4)
- O’Reilly, “헤드 퍼스트 디자인 패턴”