본문 바로가기
개발/Design Pattern

Observer Pattern

by jimmy_dev 2023. 7. 11.

한 객체(Subject)의 상태가 바뀌면 해당 객체에 의존하는 다른 객체들(Observers)에게 변경사항을 알리며 자동으로 내용이 갱신 되는 방식이다. One-to-many 의존성을 정의한다.

Subject - 주제 주체자

- Observer들이 구독하는 주제의 주체자이다. 즉, 구독 상태와 Observer들을 관리하며 변경사항에 대한 최상단의 갱신 주체는 Subject이다.
- Subject는 Observer의 존재 유무만 알면 된다. 구독 내용에 대해 전달하거나 새로운 상태로 갱신하는 역할만 존재한다.

[역할]

* notifyObservers: 상태가 변경 되면 Observer들에게 변경사항을 알린다.
* registerObservers: 구독 요청을 받으면 구독을 원하는 객체를 Observer로 등록한다
* removeObservers: 구독을 취소 요청을 한 Observer를 구독자 명단에서 제거 할 수 있다.

/// Subject Interface
abstract class Subject {
  registerObservers(Observer observer);

  removeObservers(Observer observer);

  notifyObservers();
}

Observers - 구독자(관찰자)

- 하나의 Subject에 대한 구독자로 칭한다.
- Subject가 뿌리는 구독 내용을 받아보는 것 이외에 구독 내용에 관한 직접적인 권한은 없다.

[역할]

* update(): 변경 된 주제의 상태를 받아와서 갱신한다.

/// Observer Interface
abstract class Observer {
  update();
}

Effectiveness

- 느슨한 결합의 아주 좋은 예시이다. 프로그램을 개발하다 보면 하나의 상태에 대해 여러 객체가 공유하며 사용 해야 하는 경우가 많다. 의존성을 가지되 서로에 대해 필요한 역할 및 정보만 알고 알려주는 구조를 만들 수 있다는 점에서 리팩토링을 할때, 코드 구성을 안정화 할 때 착안할 적잘한 방법이라고 느꼈다.
- 'One-to-many' 방식의 의존성을 정의한다. 의존하는 객체(Observer)들이 하나의 주제에 대한 정보를 구독하고 있다. 즉, 해당 객체들이 구독자로써 할 일은 변경 사항에 대해 지속적으로 갱신하는 일 말고는 없다. 따라서 명확한 역할 구분과 깔끔한 코드 패턴이 나올 수 있다.

Loose Coupling - 느슨한 결합

객체들이 상호작용 하며 데이터를 주고 받는 경우, 서로에 대한 의존성이 강해질 수록 코드 유지보수가 어려워진다. 또한, 인터페이스 혹은 구현 클래스에 변동이 필요할 때 쉽게 구조가 망가지며 재구성을 해야하는 비용이 더 크게 들 수 있다.
따라서, 서로에게 의존하게 되는 것은 당연하지만 서로의 추상적인 역할에 대해 알고 있는 것으로 충분하며, 각자의 역할 배분에 대해 정확하게 명시하여 분리시킬 필요가 있다.

Push vs Pull

앞서 말했듯이 Subject가 일방적으로 변경 된 상태를 갱신시켜주는 방식이다. 즉, Observer는 원하는 상태 정보만 따로 받아 볼 권한이 없는 상황이다. 이처럼 Subject의 Push 관점으로만 생각했을 때 벌어질 수 있는 일이다.

하지만 관점을 조금 바꾸어 Object가 변경된 상태를 Pull 한다면 어떠할까? 여기서 부터는 구현을 어떻게 하냐에 따라 적절한지 아닌지가 판단 된다. 구현에 따라 상황이 다르기 때문에 단정짓는 판단은 못 내린다.

하지만, 확장성과 유지보수를 생각했을 때 일방적으로 상태를 업데이트 받는 방식은 Observer를 구현하는 여러 ConcretedClass들을 구현할 때 많은 제약을 줄 수 있다. 간단한 메소드 하나만 추가하고 기존 코드를 살짝만 바꾸는 것 만으로 Observer가 원하는 상태 정보를 업데이트 할 수 있는 구조가 만들어진다면 이렇게 하는 것이 맞다는 결론이 나오지 않을까?