본문 바로가기
Book

[독서 기록] 헤드퍼스트 디자인 패턴 2장 | 객체들에게 연락 돌리기 - 옵저버 패턴

by Renechoi 2022. 11. 14.
 
헤드 퍼스트 디자인 패턴
이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나의 패턴에 하나의 이야기를 담았습니다. 틀에 박히지 않아 지루할 틈이 없는 구성과 친구와 이야기하듯 편안한 대화체로 이야기를 풀어냅니다. 이야기 속에 다양한 방법으로 해결할 수 있는 질문과 90개 이상의 연습문제를 담았습니다. 마치 게임 퀘스트를 해결하듯 문제를 하나하나 해결하다 보면 학습한 내용이 머릿속에 강렬하게 남습니다. 이유 2. 원스톱으로 배우는 14가지 GoF 핵심 디자인 패턴과 9가지 객체지향 디자인 원칙! 현장에서 자주 사용되는 옵저버, 어댑터, MVC 패턴 등 14가지 GoF 객체지향 패턴을 중점으로 패턴의 정의, 사용 시기, 사용처, 사용 이유, 즉시 디자인에 적용하는 방법을 알려줍니다. 이와 더불어 객체지향 프로그래밍에 광범위하게 적용할 수 있는 OCP, 할리우드 원칙 등 9가지 객체지향 디자인 원칙과 패턴으로 생각하는 방법도 알려줍니다. 이유 3. 시대의 변화에 맞춘 개정과 한국 독자만을 위한 특별판! 자바 8과 자바 16 이상에서 무리 없이 동작할 수 있도록 예제 코드를 수정했으며, 부가적인 설명과 Q&A 질문을 추가했습니다. 또한 16여 년 만의 개정을 기념해 오직 한국 독자만을 위한 새로운 삽화를 사용하고 한글 친화적인 구성했습니다. 원서를 읽을 때보다 더욱 편안하게 디자인 패턴을 학습할 수 있습니다. ▶ 이 책을 읽어야 하는 당신! ● 소프트웨어 출시는 완벽 그 자체! “어?~ 코드 수정하려고 다시 보니까 난리…” → 유지보수만 생각하면 그저 눈물인 주니어 (자바) 개발자 ● 코딩 실력은 장판파의 장비! “어?~ 팩토리 메소드 패턴을 이렇게 적용했던가?” → 디자인 패턴을 다시 한번 살펴보고 싶은 시니어 (자바) 개발자 ● 혼자 공부해서 다진 프로그래밍 언어 실력! “어?~ 근데 패턴이 뭐야?” → 개발 현장의 소프트웨어 디자인 방법이 궁금한 개발자 지망생
저자
에릭 프리먼, 엘리자베스 롭슨, 케이시 시에라, 버트 베이츠
출판
한빛미디어
출판일
2022.03.16

 

헤드퍼스트 디자인 패턴 1장 | 디자인 패턴 소개와 전략 패턴 

(에릭 프리먼 외 4인, 서환수 옮김, 한빛미디어)

 


 

기상 스테이션용 코드 추가하기

 

public class WeatherData {
	
    // 인스턴스 변수 선언 
    
    public void measurementsChanged() {
    	float temp = getTemerature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        currentConditionDisplay.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDisplay.update(temp, humidiy, pressure);
   }
   
   // 기타 메소드 
     
}

WeatherData에 있는 게터 메소드를 호출해서 최신 측정값을 가져옵니다. 각 값을 적당한 변수에 저장합니다. 

 

각 디스플레이를 갱신합니다. 최신 측정값을 전달하면서 각 디스플레이 항목의 update 메소드를 호출합니다. 

 

- 78p

 

- 79p

 

 

 

신문사 + 구독자 = 옵저버 패턴

- 81p

 

신문사를 주제(subject), 구독자를 옵저버(observer)라고 부른다는 것만 외워두세요. 

- 81p 

 

옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.

- 87p 

 

 

옵저버 패턴에서는 주제가 상태를 저장하고 제어합니다. 따라서 상태가 들어있는 객체는 하나만 있을 수 있습니다. 반면에 옵저버는 상태를 사용하지만, 반드시 소유할 필요는 없습니다. 따라서 옵저버는 여러 개가 있을 수 있으며, 주제에서 상태가 바뀌었다는 사실을 알려주길 기다리는, 주제에 의존적인 성질을 가지게 되죠. 그러므로 하나의 주제와 여러 개의 옵저버가 연관된 일대 다 관계가 성립됩니다. 

- 88p 

 

데이터의 주인은 주제입니다. 옵저버는 데이터가 변경되었을 때 주제에서 갱신해 주기를 기다리는 입장이기에 의존성을 가진다고 할 수 있습니다. 이런 방법을 사용하면 여러 객체가 동일한 데이터를 제어하는 방법보다 더 깔끕한 객체지향 디자인을 만들 수 있습니다.

- 88p 

 

 

디자인 원칙 : 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.

- 90p 

 

 

 

기상 스테이션 구현하기

 

인터페이스

public interface Subject {
	public void registerObserver(Observer o);
    public void removeObserver(Observer o);			 => 이 2가지 메소드는 observer를 인자로 받습니다. 각각 옵저버를 등록하고 제거하는 역할을 하죠.
    public void notifyObservers(); => 주제의 상태가 변경되었을 때 모든 옵저버에게 변경 내용을 알리려고 호출
}


public interface Observer {
	public void update(float temp, float humidity, float pressure);
}

=> 옵저버 인터페이스는 모든 업저버 클래스에서 구현해야 합니다.
따라서 모든 옵저버는 update() 메소드를 구현해야 합니다.

public interface DisplayElement {
	public void display();
}

=> DisplayElement 인터페이스에는 display()라는 메소드밖에 없습니다. 디스플레이 항목을 화면에 표시해야 하면 그 메소드를 호출합니다.

 

subject 인터페이스 구현하기

 

public class WeatherData implements Subject {
	private List<Observer> observers;
    private float tempeerature;
    private float humidity;
    private float pressure;
    
    public WeatherData() {
    	observers = new ArrayList<Observer>();
    } 
    => 옵저버 생성자 = 객체 위에 추가한 리스트 객체 생성 
    
    public void registerObserver(Observer o) {  	=> 옵저버가 등록을 요청하면 목록 맨뒤에 추가
    	observers.add(o);
    }
    
    
    public void removeObserver(Observer o) {   => 탈퇴를 요청하면 목록에서 빼기 
    	observers.remove(o);
    }
    
    public void notifyObservers() {
    	for (Observer observer : observers) {
        	observer.update(temperature, humidity, pressure);
        }
    } 
    => 정말 중요한 부분입니다.
    모든 옵저버에게 상태 변화를 알려주는 부분이죠. 
    모두 Observer 인터페이스를 구현하는, 즉 update() 메소드가 있는 객체들이므로 손쉽게 상태 변화를 알려 줄 수 있습니다. 
    
    public void measurementsChanged() {
    	notifyObservers();
    } => 기상 스테이션으로부터 갱신된 측정값을 받으면 옵저버들에게 알립니다. 
    
    
    public void setMeasurements(float temperature, float humidity, float pressure) {
    	this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    } 
    
}

 

- 94 ~ 95p 

 

 

디스플레이 요소 구현하기

public class CurrentConditionDisplay implements Observer, DisplayElement {	=> weahterData 객체로부터 변경사항을 받으려면 Observer를 구현해야 합니다.
	private float temperature;
    private float humidity;
    private WeahterData weahterData; 
    
    public CurrentConditionsDisplay(Weatherdata weatherDAta) { => 생성자에 weatherData라는 주제가 전달되며, 그 객체를 써서 디스플레이를 옵저버로 등록합니다. 
    	this.weatherData = weatherData;
        weatherData.registerObserver(this);
    } 
    
    public void update(float temperature, float humidity, float pressure) { 
    	this.temperature = temperature;
        this.humidity = humidity;   => update()가 호출되면 온도와 습도를 저장하고 display()를 호출합니다. 
        display();
    }
    
    public void display();
    	sout("현재 상태: 온도" + temperature + "F, 습도" + humidity ");
    }		=> display()메소드는 가장 최근에 받은 온도와 습도를 출력합니다. 
}

- 96p

 

 

Q. update() 메소드에서 display() 메소드를 호출하는 방법이 정말 최선인가요?

A. 지금 다루고 있는 간단한 예제에서는 값이 바뀔 때마다 display()를 호출하는 방법이 괜찮아 보입니다. 하지만 최선의 방법은 아닙니다. 데이터를 화면에 표시하는 더 좋은 방법은 12장에서 모델-뷰-컨트롤러 패턴을 배울 때 자세히 알아보도록 하죠. 

- 96p

 

 

기상스테이션 테스트 

public class WeatherStation {

	public static void main(String[] args) {
    	WeatherData weatherData = new WeatherData(); 	=> 우선 WeatherData 객체를 생성합니다. 
        
        CurrentConditionDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 
        
        weatherData.setMeasurements(80, 65, 30.4f); => 새로운 기상 측정값이 들어왔다고 가정
    }
 }

- 97p 

 

 

 

주제가 옵저버로 데이터를 보내는 푸시(push)를 사용하거나 옵저버가 주제로부터 데이터를 당겨오는 풀(pull)을 사용하는 방법 중 어느 하나를 선택하는 일은 구현 방법의 문제라고 볼 수 있습니다. 하지만 대체로 옵저버가 필요한 데이터를 골라서 가져가도록 만드는 방법이 더 좋습니다. 시간이 지남에 따라 애플리케이션은 계속 바뀌고 점점 복잡해집니다. ... 옵저버가 필요한 데이터를 당겨올 수 있도록 기상 스테이션 코드를 수정하는 일은 그리 어렵지 않습니다. 주제가 자신의 데이터에 관한 게터 메소드를 가지게 만들고 필요한 데이터를 당겨올 때 해당 메소드를 호출할 수 있도록 옵저버를 고쳐주기만 하면 됩니다. 

- 104p 

 

 

풀 방식으로 코드 바꾸기

 

1) 주제에서 알림 보내기 : 옵저버의 update 메소드를 인자 없이 호출하도록 WeatherData의 notifyObservers() 메소드를 수정합니다.

 

2) 옵저버에게 알림 받기 : Observer 인터페이스에서 update() 메소드에 매개변수가 없도록 서명을 바꿔 줍니다.

 

3) 마지막으로 update() 메소드의 서명을 바꾸고 WeatherData의 게터 메소드로 주제의 날씨 데이터를 가져오도록 각 Observer 구상 클래스를 수정합니다. 

 

- 105p 

 

 

반응형