"객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 구성하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴"
즉, 한 객체의 변화에 대한 "리스너"들을(실제 자바/스프링 구현 환경에서도 ~Listener로 네이밍된 클래스들을 만날 수 있을 것이다) 변화의 주체가되는 객체 안에 구성하는 것이다.
매우 매우 정형화된 패턴이 있으며, 자바에서 Library로 등록 된 적도 있을 정도로 패턴 사용에 대한 유연성이랄 게 잘 없다. (다만, 현재는 Deprecated. 어쨋든 디자인 패턴은 직접 사용,구현하여 유연성과 그 장점을 최대로 챙기기 위함인 듯)
그럼, 정형화된 패턴을 한번 알아보자.
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObserver();
}
"주제" 가 되는, 실제 변경이 일어나는 객체의 추상이다.
Subject를 상속하는 실제 구현체들은 3 개의 매서드를 오버라이드 받는 것을 강제받고 있으며, 이는 각각 다음과 같은 기능을 한다.
public interface Observer {
void update();
}
오케이, 알겠는데 이걸 어따 써먹나요?
예를 들어 기상청의 날씨 API를 적절한 곳에 뿌려줘야 하는 상황이 있다고 해보자. 그렇다면 다음과 같은 구현이 가능하다.
public class WeatherData implements Subject {
private Float temperature; // 기온
private Float pressure; // 기압
private List<Observer> observers;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(temperature, pressure);
}
}
public void setStatus(Float temperature, Float pressure){
this.temperature = temperature;
this.pressure = pressure;
notifyObserver();
}
}
옵저버의 리스트를 "구성" 요소로 가진다.
오버라이드한 매서드를 통해 모든 옵저버들을 업데이트 한다. (기온, 기압)
언뜻 보기엔 괜찮아 보인다. 한번 테스트 해 볼까?
public class CurrentWeather implements Observer {
private Float temperature; // 기온
private Float pressure; // 기압
@Override
public void update(Float temperature, Float pressure) {
this.pressure = pressure;
this.temperature = temperature;
display();
}
public void display(){
System.out.println("업데이트 되었음을 알립니다");
}
}
Observer 추상의 구현체인 "현재 날씨"
update 매서드를 통해서 현재 날씨 정보를 갱신한다
갱신 후, 업데이트 된 것을 표현하기 위해 display() 매서드를 사용했다.
public class ObserverPatternTest {
public static void main(String[] args) {
Subject weatherData = new WeatherData();
// 추상 타입으로 구현체 인스턴스 생성
Observer currentWeather = new CurrentWeather();
// 마찬가지
weatherData.registerObserver(currentWeather);
// currentWeather를 옵저버 목록에 추가함
weatherData.notifyObserver();
// 옵저버 목록의 모든 녀석들을 업데이트함!
}
}
하지만, 실시간 기상 데이터가 WeatherData에 꽂히고 있고, 구현체가 굉장히 많아서 구현체마다 이 데이터를 받아야 하는 시간이 각기 다르다면 어떨까?
그렇다면, Subject에서 변경을 push하는 것이 아니라 구현체마다 필요 할 때 pull 받는 게 낫지 않을까?
public class WeatherData implements Subject {
private Float temperature; // 기온
private Float pressure; // 기압
private List<Observer> observers;
public Float getTemperature() {
return temperature;
// getter 매서드 추가
}
public Float getPressure() {
return pressure;
// getter 매서드 추가
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.update();
}
}
}
public interface Observer {
void update(Subject subject);
}
public class CurrentWeather implements Observer {
private Float temperature; // 기온
private Float pressure; // 기압
@Override
public void update(Subject subject) {
if (subject instanceof WeatherData){
this.temperature = ((WeatherData) subject).getTemperature();
this.pressure = ((WeatherData) subject).getPressure();
display();
}
}
public void display(){
System.out.println("업데이트 되었음을 알립니다");
}
}
WeatherData 에 세터 매서드를 추가해서 실제 값을 바꿔 보고, 테스트 해보자
추가적으로 display() 매서드도 기온을 표시할 수 있게 해주자.
public class CurrentWeather implements Observer {
private Float temperature; // 기온
private Float pressure; // 기압
@Override
public void update(Subject subject) {
if (subject instanceof WeatherData){
this.temperature = ((WeatherData) subject).getTemperature();
this.pressure = ((WeatherData) subject).getPressure();
display(temperature, pressure);
}
}
public void display(Float temperature, Float pressure){
System.out.println("기온은 (" + temperature + "), 기압은 (" + pressure + ") 입니다.");
}
}
위와 같이, pull 방식으로 Observer 스스로가 필요 할 때 데이터를 불러오고 사용 가능하다. (이 방식이 더 안전하다!)
이거 완전 구독 아니냐? Pub/Sub 패턴 이거랑 똑같으니까 패턴 하나 날먹했누 ㅋㅋ
아니라고 한다. 링크
PUB/SUB 패턴은 중간에 메시지 브로커, 이벤트 버스가 존재한다 이 이유 때문에 아래 모든 장점을 가진다.
따라서 "결합도가 더 낮다"
더해서, 비동기식으로 운영되며
크로스 도메인에서 사용 가능하다.
스프링 DI, IoC 를 처음 배울 때 처음 알았던 "느슨한 결합", 어떤 목적을 가져야 하는것일까?
새로운 기능을 개발하거나, 기존 기능을 수정하고 확장하는 게 쉽다 ( 결합이 느슨하므로, 결합된 코드가 많이 수정되거나 완전히 바뀔 일이 없다 )
유지 보수가 쉽다 ( 위와 같은 이유)
원칙 : 두 객체가 상호작용 하지만, 서로에 대해서 잘 몰라야 한다