예제 코드로 이해해보는 디자인 패턴 - 옵저버 패턴(Observer Pattern)

kindof

·

2023. 11. 3. 22:46

0. 옵저버 패턴(Observer Pattern)?

옵저버 패턴(Observer Pattern)은 주체(Subject)와 관찰자(Observer) 사이의 관계를 정의하고 주체의 상태(State)가 변경될 때, 이 변경사항을 관찰자에 전파하는 패턴입니다.

 

[헤드 퍼스트 디자인 패턴(개정판) 14가지 GoF 필살 패턴! 유지 관리가 편리한 객체지향 소프트웨어를 만드는 법] 책에서는 아래와 같은 상황으로 옵저버 패턴을 설명하는데요.

  • 신문사가 사업을 시작하고 신문을 찍어낸다.
  • 독자가 특정 신문사에 구독 신청을 하면 매번 새로운 신문이 나올 때마다 배달을 받을 수 있다. 구독을 해지하기 까지 신문을 계속 받을 수 있다.
  • 신문을 더 이상 보고 싶지 않으면 구독 해지 신청을 한다. 그러면 더 이상 신문이 오지 않는다.
  • 신문사가 망하지 않는 이상 개인, 호텔, 항공사 등은 꾸준하게 신문을 구독하거나 해지할 수 있다.

 

이번 글에서는 책에서 소개한 상황을 실제 코드로 구현해보면서 옵저버 패턴을 이해해보려고 합니다.

 

1. 코드로 실습하기

@Data
public class News {

    private String title;
    private String content;

}

데이터로 사용할 News 클래스입니다. 간단히 제목과 내용만을 가지도록 만들었습니다.

 

public interface NewsSubject {

    void addObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObservers();

}

NewsSubject는 신문사에 대한 Subject 인터페이스입니다. Subject 인터페이스는 옵저버를 추가, 제거하는 메서드와 상태의 변경이 일어났을 때 구독 중인 옵저버들에게 변경을 전파하는 notifyObservers() 메서드를 정의합니다.

 

@Getter
public class NewsCompany implements NewsSubject {

    private List<Observer> observers = new ArrayList<>();
    private News news;

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

NewsCompany 클래스는 Subject 인터페이스를 구현했습니다. 자신을 구독하는 Observer 목록을 가지고 있으며, 각각의 옵저버를 추가, 제거하는 메서드를 정의하고 변경이 일어났을 때에는 각 옵저버에 정의되어 있는 update() 메서드를 호출합니다.

 

이 때, update() 메서드에 NewsCompany가 가지고 있는 상태값(News)을 전달하지 않는데요.

 

이는 바로 뒤에서 설명하겠지만 Subject가 옵저버에게 데이터를 보내는 것이 아닌 옵저버가 Subject로부터 데이터를 끌어오는 Pull 방식을 사용함으로써 애플리케이션을 쉽고 확장성있게 만들기 위한 목적입니다.

 

한편, NewsSubject는 구체적인 옵저버 구현체들이 Observer 인터페이스를 구현한다는 것만 알고 작성되었기 때문에 Subject - Observer 사이에 느슨한 결합을 제공하게 됩니다.

 

public interface Observer {

    void update();

}

Observer 인터페이스입니다. Observer 인터페이스는 각 옵저버들이 변경사항을 받았을 때 실행 될 update() 메서드만을 정의합니다. java.util에서 기본으로 제공하는 Observer 인터페이스가 있는데 Java 9 버전 이후에 Deprecated 되었습니다.

 

@Setter
public class ACompany implements Observer {

    private NewsCompany newsCompany;
    private News news;

    public ACompany(NewsCompany newsCompany) {
        this.newsCompany = newsCompany;
        newsCompany.addObserver(this);
    }

    @Override
    public void update() {
        News updatedNews = newsCompany.getNews();
        setNews(updatedNews);
    }
}

신문사를 구독하는 회사 클래스이자 하나의 옵저버 구현체입니다.

 

생성자에서 NewsCompany Subject를 받아 등록하고, update() 메서드에서는 Subject의 데이터를 Getter 메서드로 가져와 변경사항을 업데이트합니다.

 

@Setter
public class BSchool implements Observer {

    private NewsCompany newsCompany;
    private News news;

    public BSchool(NewsCompany newsCompany) {
        this.newsCompany = newsCompany;
        newsCompany.addObserver(this);
    }

    @Override
    public void update() {
        News updatedNews = newsCompany.getNews();
        setNews(updatedNews);
    }
}

또 하나의 구현체인 BSchool 클래스입니다. 위 코드는 예시 코드라서 작성된 내용이 똑같지만 회사, 학교 등의 클래스는 서로 다른 필드와 메서드들이 존재할 수 있겠습니다.

 

@Test
void observer_pattern_test() {
    NewsCompany newsCompany = new NewsCompany();
    ACompany aCompany = new ACompany(newsCompany);
    BSchool bSchool = new BSchool(newsCompany);

    newsCompany.setNews(new News("hello!", "everyone!"));
    newsCompany.notifyObservers();

    List<Observer> observers = newsCompany.getObservers();
    String ACompanyNewsTitle = aCompany.getNews().getTitle();

    Assertions.assertEquals(2, observers.size());
    Assertions.assertEquals("hello!", ACompanyNewsTitle);
}

테스트 성공

 

이제 위에서 작성된 코드들을 바탕으로 간단한 테스트 코드를 작성해봤습니다.

 

aCompany, bSchool 두 개의 옵저버 구현체들을 생성하고 newsCompany의 데이터가 변화했을 때 notifyObservers() 메서드를 호출했습니다.

 

검증 부분에서 볼 수 있듯이 Subject는 2개의 옵저버를 가지고 있으며, 변경된 데이터가 aCompany에 정상적으로 업데이트 된 것을 볼 수 있습니다.