* 본 포스팅은 한빛미디어의 헤드퍼스트 디자인 패턴을 공부한 내용을 정리한 글입니다.
저의 부족한 생각과 주관으로 틀린 내용이 있을 수 있으니,
자세한 내용이 궁금하시다면 해당 책을 읽어보시길 추천드립니다.
핵심
- 느슨한 결합 subject는 observer에게 값 갱신만 알려줄 뿐, 구성엔 관여하지 않음 반대로 observer는 subject에게 값 갱신만 받을뿐, 구성엔 관심이 없음
- observer패턴의 push와 pull push는 observer에게 갱신을 알리며 데이터를 전달한다 pull은 갱신만 알리며, observer는 subject의 getter method를 통해 필요한 값만 가져온다.
Weather-O-Rama의 차세대 인터넷 기반 기상 스테이션 구축하기!
구조
- WeatherData 객체 : 온도, 습도, 기압을 추적
- 디스플레이 객체 : 현재 기상상태, 기상 통계, 간단한 기상 예보를 보여주는 세가지 객체 존재
- 특징 : 측정치가 수집될때마다 실시간으로 갱신 됨
- 요구사항 : 이후 손쉽게 새로운 디스플레이를 추가할 수 있도록 확장가능해야 함
구현
public classs WeatherData{
float temperature;
float humidity;
float pressure;
public void mesurementsChanged(){
float temp = getTemperature();
float humidity = getHmuidity();
float pressure = getPressure();
currentConditionDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
//각 data들의 gettere들과 기타 메서드
}
여러모로 개선해야 될 점이 많아보이는 코드군요!
디스플레이 항목들이 update 메서드를 동일하게 사용하는것으로 보아 공통적인 인터페이스를 구현했고,
WeatherData클래스는 캡슐화가 되어 있습니다.
그러나, 디스플레이에 update하는 부분이 구체적인 구현을 바탕으로 개발되어 있습니다.
그리고 바뀔 수 있는 부분이지만, 캡슐화 되지도 않았구요.
그렇기에 새로운 디스플레이 항목이 추가될 때 마다 코드를 수정해야 하며,
동적으로 디스플레이 항목을 추가하거나 제거할수가 없습니다!
이를 옵저버 패턴을 사용해서 새로 구현해 보기로 하겠습니다.
옵저버 패턴이란 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로, 일대다 의존성을 정의합니다.
말이 좀 어렵죠? 쉽게 설명하지면 아래와 같습니다.
Subject - Observer로 구성된 패턴입니다.
Subjet는 데이터를 관리하며, 데이터가 바뀌면 Observer에게 전달합니다.
Observer는 Subject를 구독하여 데이터 바뀌면 갱신 내용을 전달받습니다.
Subject는 여러 Observer를 목록으로 관리하게 됩니다.
옵저버 패턴의 구조를 한번 볼까요?
주제와 옵저버를 인터페이스로 구현해 구상클래스로 구현하면 됩니다.
주제의 구상 클래스는 옵저버의 구상클래스들을 목록에 추가하고 제외할수 있으며, 값이 변경되면 notyObserver 메서드로 목록의 옵저버들에게 연락하게 됩니다.
그리고 옵저버의 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될 수 있습니다!
여기서 느슨한 결합에 대해서 언급할수 있겠네요!
느슨한 결합
- 객체 사이의 상호의존성을 최소화 하기 떄문에,
변경사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축하는 방법.
주제는 옵저버가 옵저버 인터페이스를 구현한다는 사실만 압니다. 그외에는 알 필요가 없죠.
그렇기에 옵저버 인터페이스만 구현한다면 언제든지 추가할 수 있고, 추가한다해도 주제를 변경할 필요가 없습니다. 다른 용도로 써도 손쉽게 재사용이 가능하구요.
주제나 옵저버 인터페이스를 구현한다는 조건만 만족한다면 어떻게 수정해도 문제가 생기지 않습니다! 상호의존성이 최소화 된 것이죠.
그러므로, 상호작용하는 객체 사이엔 가능하면 느슨한 결합을 사용해야 합니다.
Weather-O-Rama의 구조를 옵저버 패턴으로 구현한다면, 아래처럼 구성이 되겠죠?
조금 복잡해 보이지만, 디스플레이가 좀 많아 그렇습니다.
옵저버 인터페이스를 구현하기만 하면 어떤 클래스든 옵저버가 될 수 있으므로, 각 디스플레이들은 디스플레이와 옵저버 인터페이스를 구현하면 됩니다!
이렇게 구현한다면 다른 디스플레이를 개발해도 위에 설명한것처럼 어떤 코드도 수정하지 않아도 됩니다. 굉장히 유연하죠.
그럼, 설계도 마쳤으니 구현해 보도록 하겠습니다!
인터페이스
1. 주제 인터페이스
public interface Subject{
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notiyObserver();
}
2. 옵저버 인터페이스
public interface Observer{
public void update(float temp, float humidity, float pressure);
}
3. 디스플레이 인터페이스
public interface DisplayElemet{
public void display();
}
인터페이스 구현
1. WeatherData (implement Subject)
public class WeatherData implements Subjet{
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notiyObserver() {
for(Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notiyObserver();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}
//기타 여러 메속드들...
}
2. CurrentConditionDisplay (implement Observer, Display)
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("현재 상태: 온도 " + temperature + "F, 습도 " + humidity + "%");
}
}
3. Main (실행해보기!)
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);
//소스가 많아서 아래 두개는 구현을 제외했습니다~!!
//StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
//ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
* 좀더 생각해보기
옵저버들이 데이터가 갱신될떄 필요한 데이터만 가져오게 한다면, 코드를 좀 더 일반화 할수 있죠.
주제가 옵저버로 데이터를 보내는것은 Push라 하고, 옵저버가 주제로부터 데이터를 당겨오는것을 pull이라고 합니다.
위 프로젝트를 풀로 구현해 보겠습니다.
1. 옵저버 인터페이스 수정
public interface Observer{
public void update();
}
이제 옵저버는 update를 통해 데이터를 받는 것이 아니라, 값이 갱신됬다는 신호만 받을것이므로
update()메서드의 파라미터를 비워 줍니다.
2. WeatherData(주제) 수정
public class WeatherData implements Subjet{
//...
@Override
public void notiyObserver() {
for(Observer observer : observers) {
observer.update();
}
}
//...
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
주제에서도 마찬가지로 Observer.update()메소드를 Observer의 수정사항에 맞춰 파라미터를 비워줍니다.
그리고, 옵저버가 WeatherData의 데이터에 접근해 값을 가져갈 수 있도록 getter 메서드들을 생성합니다.
3. CurrentConditionsDisplay (옵저버) 수정
public class CurrentConditionsDisplay implements Observer, DisplayElement {
//...
public void update() {
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity;
display();
}
//...
}
옵저버는 이제 더이상 update()메서드로부터 직접적으로 파라미터를 제공받지 않습니다.
갱신이 된다면, 옵저버가 필요한 데이터만 getter메서드를 사용해 WeatherData로부터 가져옵니다.
'개발공부 > Code design' 카테고리의 다른 글
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 팩토리 메서드 (0) | 2022.09.25 |
---|---|
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 심플 팩토리 (0) | 2022.09.25 |
헤드퍼스트 디자인 패턴 03.데코레이터(Decorator) 패턴 (0) | 2022.08.08 |
헤드퍼스트 디자인 패턴 1장. 전략(Strategy)패턴 (1) | 2022.07.17 |
MVC 패턴 (0) | 2021.06.02 |