본문 바로가기
백엔드

[디자인 패턴] 옵저버 패턴

by hseong 2023. 6. 19.

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

  • 옵저버 패턴은 주제(Subject)와 옵저버(Observer) 간의 일대다 관계로 구성됩니다.
  • 주제의 상태가 바뀌면 옵저버에게 정보가 제공됩니다.
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObserver();
}
public interface Observer {
    void update();
}
  • Subject 인터페이스
    • 옵저버를 등록하고, 삭제하고, 상태가 변화했음을 알립니다.
  • Observer 인터페이스
    • 주제의 상태가 바뀌었을 때 호출되는 update() 메서드만을 가집니다.
  • Subject는 Observer를 구현하는 객체의 목록에만 의존합니다. 때문에 언제든지 새로운 Observer를 추가할 수 있습니다. Subject는 전혀 변경할 필요가 없습니다.
  • 이를 바탕으로 뉴스와 뉴스의 구독자들을 구현해보겠습니다.
public class News implements Subject {
    private final List<Observer> subscribers = new ArrayList<>();
    private String title;
    private String contents;
    private String crosswords;

    @Override
    public void registerObserver(Observer o) {
        subscribers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        subscribers.remove(o);
    }

    @Override
    public void notifyObserver() {
        subscribers
                .forEach(Observer::update);
    }

    public void updateNews() {
        notifyObserver();
    }

    public void setNewsContents(String title, String contents, String crosswords) {
        this.title = title;
        this.contents = contents;
        this.crosswords = crosswords;
        notifyObserver();
    }

    public String getTitle() {
        return title;
    }

    public String getContents() {
        return contents;
    }

    public String getCrosswords() {
        return crosswords;
    }
}
  • News 클래스는 Subject 인터페이스를 구현합니다.
  • Observer 인스턴스를 담아두기 위한 목록을 가집니다.
  • title, contents, crosswords에 관한 상태를 가지고 상태에 변화가 발생할 때마다 Observer를 구현한 뉴스 구독자들에게 상태가 변화했음을 알릴 것입니다.
public class Batman implements Observer {
    private String title;
    private String contents;
    private String crosswords;
    private News news;

    public Batman(News news) {
        this.news = news;
        news.registerObserver(this);
    }

    @Override
    public void update() {
        title = news.getTitle();
        contents = news.getContents();
        crosswords = news.getCrosswords();
    }
}
public class Joker implements Observer {
    private String title;
    private String contents;
    private News news;

    public Joker(News news) {
        this.news = news;
        news.registerObserver(this);
    }

    @Override
    public void update() {
        this.title = news.getTitle();
        this.contents = news.getContents();
    }
}
  • Batman과 Joker는 Observer 인터페이스를 구현합니다.
  • 생성자에서 news를 인수로 받아 상태로 가지고 자기자신을 news의 구독자로 등록합니다.
  • Subject 인스턴스는 상태의 변경이 발생할 때마다 Observer 인스턴스의 update() 메서드를 호출할 것입니다. update()가 호출되면 news 인스턴스로부터 데이터를 당겨와서 사용하게 됩니다. 이렇게 데이터를 당겨와서 사용하게 되면 News 클래스에 새로운 속성이 추가되어도 Observer를 구현한 클래스들은 아무런 변경 없이 사용가능 합니다.
  • 만일 위와 같이 Observer가 Subject로부터 데이터를 당겨오는 방식(pull)이 아니라 Subject가 Observer로 데이터를 보내는 방식(push)이라면
public interface Observer {
    void update(String title, String contents, String crosswords);
}
  • Subject를 구현한 클래스에 새로운 속성을 추가하려고 할 때 Observer 인터페이스의 메서드 시그니처를 변경해야 할 것이고 이를 구현한 클래스 모두에 대해 변경이 발생하게 될 것입니다.

이러한 옵저버 패턴은 다음의 경우에 사용될 수 있습니다.

  • 한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때
  • 실제 객체 집합들을 미리 알 수 없거나 이러한 집합들이 동적으로 변경될 때

[참고]
헤드퍼스트 디자인패턴
https://refactoring.guru/ko/design-patterns/observer