컴퓨터과학/0 + 소프트웨어 아키텍처(디자인 패턴)

[소프트웨어 아키텍처] 4. 옵저버 패턴(Observer Pattern -java)

힘들면힘을내는쿼카 2022. 10. 10. 17:57
728x90
반응형

옵저버 패턴

옵저버 패턴이란 한 객체의 상태가 변화하면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.

 

예를들어 정보통신대대라는 객체의 상태가 변화하면

그객체에 의존하는 병사 및 간부들에게 연락이 가고

자동으로 전달사항이 갱신되는 방식이다.

 

 <사진 출처 장삐쭈>

옵저버 패턴은 일련의 객체 사이에서 일대다 관계를 정의

 

또한, 신문 구독 매커니즘을 이해하고 있으면 옵저버 패턴을 쉽게 이해할 수 있다.
신문사를 주제(subject) 구독자를 옵저버(observer)라고 부른다고 알고 있으면 됩니다.!

주제(subject)가 상태를 저장하고 제어한다…!

 

e.g) 행보관(subject)은 매일 아침7시에 병사들(obsever)에게 아침점호 방식을 알려줍니다.

 

한 객체의 상태가 변경되면,
그 객체에 의존하는 모든 객체에 연락이 간다.

 

옵저버 패턴의 구조

 

  • Subject
    • 객체를 옵저버로 등록하거나 탈퇴하고 싶을 때는 이 인터페이스에 있는 메서드를 사용
  • Observer
    • 옵저버가 될 가능성이 있는 객체는 반드시 해당 인터페이스를 구현해야한다.
    • Subject의 상태가 바뀌었을때 호출되는 update() 메서드밖에 없다.
  • ConcreteObserver
    • Observer 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될수 있다.
    • 각 옵저버는 특정 주제에 등록해서 연락 받을수 있다.
  • ConcreteSubject
    • 주제 역할을 하는 구상 클래스에는 항상 Subject 인터페이스를 구현해야 한다.
    • 주제 클래스에는 등록 및 해지용 메서드와 상태가 바뀔 때마다 모든 옵저버에게 연락하는 notifyObserver()메서드도 구현해야 함

 

느슨한 결합(Loose Coupling)

옵저버 패턴은 느슨한 결합을 보여주는 훌륭한 예 입니다.
느슨한 결합은 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계를 의미합니다.

느슨하게 결합하는 디자인을 사용하면 변경사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구출할 수 있습니다. 
객체사이의 상호의존성을 최소화 할 수 있기 때문입니다..!

 

 

subject는 Observer가 특정 인터페이스를 구현한다는 사실만을 압니다.

Observer의 구상 클래스가 무엇인지, Observer가 무엇을 하는지 알 필요도 없습니다.

 

 

Observer는 언제든지 새로 추가할 수 있습니다.

subject는 Observer 인터페이스를 구현하는 객체의 목록에만 의존하므로 언제든지 새로운 Observer를 추가할 수 있습니다.
실행 중에 하나의 옵저버를 다른 Observer로 바꿔도 주제는 계속해서 다른 Observer에 데이터를 보낼 수 있습니다.
마찬가지로 아무 때나 Observer를 제거해도 됩니다.

 

 

새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없습니다.

Observer가 되어야하는 새로운 구상 클래스가 생겼다고 가정 해봅시다.
이때도 새로운 클래스 형식을 받아들일 수 있도록 주제를 바꿔야 할 필요는 없습니다.
새로운 클래스에서 Observer 인터페이스를 구현하고 Observer로 등록하기만 하면 되니까요..!^^
subject는 신경 쓸 필요 없습니다.
Observer 인터페이스만 구현한다면 어떤 객체에도 연락이 가능하기 때문이죠!

 

 

subject와 Observer는 서로 독립적으로 재사용할 수 있습니다.

subject나 Observer를 다른 용도로 활용할 일이 있다고 해도 손쉽게 재사용할 수 있습니다.
주제와 옵저버 서로 단단하게 결합되어 있지 않기 때문입니다.

 

subjectObserver가 달라져도 서로에게 영향을 미치지는 않습니다.

서로 느슨하게 결합되어 있으므로 subject나 Observer 인터페이스를 구현한다는 조건만 만족한다면,
어떻게 고쳐고 문제가 생기지 않습니다.!

 

가상 모니터링 애플리케이션 개발

각종 장비들로 부터 데이터를 받아 화면에 뿌려주는 애플리케이션을 개발한다고 가정해봅시다!
데이터는 WeatherData를 통해서 받고 화면은 3가지가 있습니다.

 

 

WeatherDataDto

@ToString(of = {"temp", "hum", "press"}) //lombok 사용
public class WeatherDataDto {
    private float temp;
    private float hum;
    private float press;

    public WeatherDataDto(float temp, float hum, float press) {
        this.temp = temp;
        this.hum = hum;
        this.press = press;
    }
}

 

Subject

public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}

 

WeatherData

public class WeatherData implements Subject {

    private List<Observer> observers;
    private WeatherDataDto weatherDataDto;

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

    //옵저버 등록
    @Override
    public void registerObserver(Observer observer) {
        this.observers.add(observer);
    }

    //옵저버 삭제
    @Override
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
        System.out.println("옵저버 삭제");
    }

    //옵저버에게 알림
    @Override
    public void notifyObserver() {
        for (Observer o : observers) {
            o.update(this.weatherDataDto);
        }
    }

    //옵저버에게 알림 캡슐화
    public void measurementsChange() {
        notifyObserver();
    }

    //데이터 저장
    public void setMeasurements(WeatherDataDto weatherDataDto) {
        this.weatherDataDto = weatherDataDto;
        measurementsChange();
    }
}

 

Observer

public interface Observer {
    void update(WeatherDataDto weatherDataDto);
}

 

Display

public interface Display {
    void display();
}

 

Display1

public class Display1 implements Observer, Display {

    private WeatherDataDto weatherDataDto;
    private WeatherData weatherData;

    public Display1(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(WeatherDataDto weatherDataDto) {
        this.weatherDataDto = weatherDataDto;
        display();
    }

    @Override
    public void display() {
        System.out.println("디스플레이1="+weatherDataDto);
    }
}

 

Display2

public class Display2 implements Observer, Display {

    //..Display1과 동일//

    @Override
    public void display() {
        System.out.println("디스플레이2="+weatherDataDto);
    }
}

 

Display3

public class Display3 implements Observer, Display {

       //..Display1과 동일//

    @Override
    public void display() {
        System.out.println("디스플레이3="+weatherDataDto);
    }
}

 

WeatherSimulator

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

          //옵저버 생성
        Observer display1 = new Display1(weatherData);
        Observer display2 = new Display2(weatherData);
        Observer display3 = new Display3(weatherData);

        //날씨 데이터 변경
        weatherData.setMeasurements(new WeatherDataDto(10f, 22.2f, 30f));

          //옵저버 삭제
        weatherData.removeObserver(display2);
        weatherData.setMeasurements(new WeatherDataDto(11f, 11.1f, 20f));
    }
}

 

결과

디스플레이1=WeatherDataDto(temp=10.0, hum=22.2, press=30.0)
디스플레이2=WeatherDataDto(temp=10.0, hum=22.2, press=30.0)
디스플레이3=WeatherDataDto(temp=10.0, hum=22.2, press=30.0)
옵저버 삭제
디스플레이1=WeatherDataDto(temp=11.0, hum=11.1, press=20.0)
디스플레이3=WeatherDataDto(temp=11.0, hum=11.1, press=20.0)

 

정리

옵저버 패턴은 한 객체의 상태가 바뀌면

그 객체에 의존하는 다른 객체에게 연락이 가고

자동으로 내용이 갱신되는 방식으로

일대다 의존성을 정의한다.

 

 

728x90
반응형