"헤드 퍼스트 디자인 패턴(개정판)"을 읽고 정리한 내용입니다.
2. 객체들에게 연락 돌리기
옵저버 패턴
옵저버 패턴(Observer Pattern)은 객체들 사이에서 일대다(one-to-many) 관계를 정의해서 하나의 객체의 상태가 변할 때 모든 의존성 있는 객체들에게 자동으로 통지하고 업데이트하는 패턴이다.
신문사(Publisher)와 구독자(Subscriber)로 이루어지는 신문 구독 서비스를 떠올리면 이해하기 쉽다.
디자인 원칙
- 애플리케이션에서 달라지는 부분을 식별하고 불변으로부터 그것들을 분리해라. (Identify the aspects of your application that vary and separate them from what stays the same.)
- 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화(encapsulate)한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 구현이 아니라 인터페이스에 프로그래밍해라. (Program to an interface, not an implementation.)
- 특정 행동만을 목적으로 하는 클래스의 집합을 만들고, 이 클래스에서 구현한다. 그러면 슈퍼 클래스에서 구체적으로 구현하거나 서브 클래스 자체에서 별도로 구현하지 않아서 특정 구현에 의존하지 않는다.
- 상속보다는 구성이 더 낫다. (Favor composition over inheritance.)
- 여기서 구성(composition)은 행동을 상속받는 것 대신에, 올바른 행동 객체로 구성되어 행동을 부여받는 것을 말한다.
- 구성을 사용하면 유연성을 향상 시킬 수 있다. 알고리즘군을 별도의 클래스로 캡슐화 할 수 있을 뿐만 아니라, 구성 요소로 사용하는 객체에서 올바르게 행동 인터페이스를 구현하기만 한다면 실행 중에(runtime) 동적으로 행동을 바꿀 수도 있다.
- 상호작용하는 객체들 사이에는 느슨한 결합을 사용하도록 노력해라. (Strive for loosely coupled designs between objects that interact.)
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 주제(Subject)는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 알고있다. 즉, 주제는 옵저버가 무엇을 하는지 알 필요가 없다.
- 옵저버는 언제든지 새로 추가할 수 있다. (제거도 마찬가지이다.)
- 새로운 타입의 옵저버를 추가할 때도 주제를 수정할 필요가 없다.
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
예시
public interface Observer {
public void update(float temperature, float humidity, float pressure);
}
public interface DisplayElement {
public void display();
}
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
import java.util.ArrayList;
import java.util.List;
public class WeatherDate implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherDate() {
observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
// int i = observers.indexOf(o);
// if (i >= 0) {
// observers.remove(i);
// }
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherDate weatherDate;
public CurrentConditionsDisplay(WeatherDate weatherDate) {
this.weatherDate = weatherDate;
weatherDate.registerObserver(this);
}
@Override
public void display() {
System.out.println("현재 상태: 온도 " + temperature + "F, 습도 " + humidity);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
}
public class WeatherStation {
public static void main(String[] args) {
WeatherDate weatherDate = new WeatherDate();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherDate);
weatherDate.setMeasurements(80, 65, 30.4f);
}
}
// Output
현재 상태: 온도 80.0F, 습도 65.0