HeadFirst 02. 옵저버 패턴

Jiyeong·2022년 6월 18일
0

GoF

목록 보기
2/5

객체들에게 연락 돌리기

Observer Pattern

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의.

A. 인터페이스가 아닌 구체적인 구현을 바탕으로 코딩함.

B. 새로운 디스플레이 항목이 추가될 때마다 코드를 변경해야 함.

C. 실행 중에는 디스플레이 항목을 추가하거나 제거할 수 없음.

E. 바뀌는 부분을 캡슐화하지 않음.

p.78

느슨한 결합(Loose Coupling)

객체들이 상호작용 할 수 있지만, 서로를 잘 모르는 관계를 의미함.
  • 주제(Subject)는 옵저버가 특정 인터페이스(Observer Interface)를 구현한다는 사실만 앎.
  • 옵저버는 언제든지 새로 추가될 수 있음.
  • 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없음.
  • 주제와 옵저버는 서로 독립적으로 재사용할 수 있음.
  • 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않음.

객체지향 원칙

상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용.

디자인 원칙 경시 대회로 보는 Summary

1. 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다. - 옵저버 패턴에서 변하는 것은 주제의 상태와 옵저버의 개수, 형식이다. - 옵저버 패턴에서는 주제를 바꾸지 않고도 주제의 상태에 의존하는 객체들을 바꿀 수 있다. - 나중에 바뀔 것을 대비해 두면 편하게 작업이 가능하다.
2. 구현보다는 인터페이스에 맞춰서 프로그래밍 한다. - 주제와 옵저버에서 모두 인터페이스를 사용했다. - 주제는 Subject 인터페이스, Observer 인터페이스를 구현하는 객체들의 등록과 탈퇴를 관리하고, 그런 객체들에게 연락을 돌림. - 이렇게 되면 느슨한 결합이 가능함.
3. 상속보다는 구성을 활용한다. - 옵저버 패턴에서는 구성을 활용해서 옵저버들을 관리한다. - 주제와 옵저버 사이의 관계는 구성으로 이뤄지며, 실행 중 구성되는 방식을 사용한다.

낱말 퀴즈로 보는 Summary

- 하나의 주제가 여러 개의 옵저버에 정보를 보냄. - 주제는 원래 모든 데이터를 옵저버에 푸시하는 방식으로 보내고자 함. - 주제는 인터페이스다. - 결합은 느슨한 것이 좋다. - 프로그램을 짤 때 구현보다는 인터페이스에 맞추는 것이 좋다. - 주제는 옵저버들을 잘 몰라도 괜찮다. - 알림 시 순서에 의존하지 않도록 주의해야 한다. - 옵저버는 주제에 의존적입니다.

기상 스테이션을 구현하는 인터페이스들

public interface DisplayElement {
    public void display();
} 

public interface Observer {
    public void update();
    //기상 정보가 변경되었을 때 옵저버에게 전달되는 상태값들.
    //이 인터페이스는 모든 옵저버 클래스에서 구현되어야 함. 그래서 모든 옵저버들은 update() 메소드를 구현해야 한다.
}

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    //위 두 메소드들은 Observers을 인자로 받음. 각각 옵저버를 등록하거나 제거하는 역할.
    public void notifyObservers();
    //주제의 상태가 변경되었을 때 모든 옵저버에게 변경 내용을 알리려고 호출되는 메소드.
}

Subject 인터페이스 구현하기

public class WeatherData implements Subject{
    //인스턴스 변수 선언
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observers = new ArrayList<Observer>();
    }

    //Subject 인터페이스를 구현하는 부분
    public void registerObserver(Observer o){//옵저버가 둥록 요청 시 목록 맨 뒤에 추가하기만 하면 됨
        observers.add(o);
    }

    public void removeObserver(Observer o){//옵저버가 탈퇴 요청 시 목록에서 빼기만 하면 됨
        observers.remove(o);
    }

    public void notifyObserver(){
        for(Observer observer : observers){
            observer.update();
        }
    }
    /* 중요한 부분 - 모든 옵저버에게 상태 변화를 알려줌*/
    // 모두 Observer 인터페이스를 구현하는 update() 메소드가 있는 객체들이므로 상태 변화를 쉽게 알려줄 수 있음.

    public void measurementsChanged(){
        //가상 스테이션으로부터 갱신된 측정 값을 받으면 옵저버들에게 알림.
        notifyObserver();
    }

    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{
    // Observer - WeatherData 객체로부터 변경 사항을 받으려면 구현해야 함.
    // DisplayElement - API 구조상 모든 디스플레이 항목에서 DisplayElement를 구현하기로 했기에 이 인터페이스를 구현함.

    private float temperature;
    private float humidity;
    private WeatherData weatherData;

    public CurrentConditionsDisplay(WeatherData weatherData){
        //생성자에 weatherData라는 주제가 전달되며, 그 객체를 써서 디스플레이를 옵저버에 등록함.
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    public void update(){
        this.temperature = weatherData.getTemperature();
        this.humidity = weatherData.getHumidity();
        //Subjcet의 게터 메소드 사용
        display();
        //update가 호출되면 온도와 습도를 저장하고 display()를 호출
    }

    public void display(){
        System.out.println("현재 상태: 온도" + temperature + 'F, 습도' + humidity + "%");
        //display() 메소드는 가장 최근의 온도와 습도를 출력
    }
}

기상 스테이션 테스트

public class WeatherStation{
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78 , 90, 29. 2f);
    }
}
public void measurementsChanged(){

    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();

    //아래 부분들은 바뀔 수 있으므로 캡슐화 필요!
    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);

}

애플리케이션 테스트

public class SwingObserverExample{
    JFrame frame;
    public static void main(String[] args) {
        //프레임을 만들고 그 안에 버튼을 추가하는 간단한 스윙 애플리케이션
        SwingObserverExample example = new SwingObserverExample();
        example.go();
    }

    public void go(){
        frame = new JFrame();

        JButton button = new JButton("할까?말까?");
        //람다식으로 구현
        button.addActionListener(event->
                System.out.println("하지마! 아마 후회할 걸?"));
        button.addActionListener(event->
                System.out.println("그냥 해 봐"));
    }
} 
profile
깃스타가 되고 싶은 벨플루언서

0개의 댓글