Exist Technote

[Python] Observer Pattern (옵저버 패턴) 본문

Design Pattern/Behavioral

[Python] Observer Pattern (옵저버 패턴)

by_Exist 2020. 10. 10. 18:54

병사들이 간부의 명령에 따라 일괄적으로 행동을 취한다. 출처 - https://namu.wiki/w/%ED%8C%8C%EC%9D%BC:external/ojsfile.ohmynews.com/10zzung_325728_1%255B540326%255D.jpg

Observer Pattern이란?

  • 감시자 패턴.
  • 객체들 사이에 일대 다의 의존 관계를 정의하여, 일 객체의 상태가 변할 때 다 객체들이 변화를 통지받아 동작을 수행할 수 있게 만드는 패턴.
  • 구독-발행 패턴과 유사점이 많다.

예제

from contextlib import suppress


# 옵저버들을 관리하는 객체 subject. 확장할 수 있도록 설계된 구조가 멋지다.
class Subject:

    # 옵저버를 추가 or 제거
    def __init__(self) -> None:
        self._observers = []

    def attach(self, observer) -> None:
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer) -> None:
        # contextlib.suppress()
        # 인자로 전달된 예외가 발생할 경우 예외를 무시하고 with 블럭을 빠져나간다.
        with suppress(ValueError):
            self._observers.remove(observer)

    # 옵저버들의 update 메소드를 일괄적으로 실행
    def notify(self, modifier = None) -> None:
        for observer in self._observers:
            if modifier != observer:    # 타겟 옵저버 필터링 조건.
                observer.update(self)


# Subject를 확장하여 활용하는 data
class Data(Subject):
    def __init__(self, name: str = "") -> None:
        super().__init__()
        self.name = name
        self._data = 0

    @property
    def data(self) -> int:
        return self._data

    # 프로퍼티를 이렇게 활용할 수 있다니. 멋진데?
    @data.setter
    def data(self, value: int) -> None:
        self._data = value
        self.notify()


# 옵저버의 인터페이스를 정의하는 클래스 Observer
class Observer:
    def update(self, subject) -> None:
        pass


# 구현된 옵저버 클래스들. Observer 인터페이스를 따른다면 얼마든지 추가될 수 있다.
class HexViewer:
    def update(self, subject: Data) -> None:
        print(f"HexViewer: Subject {subject.name} has data 0x{subject.data:x}")


class DecimalViewer:
    def update(self, subject: Data) -> None:
        print(f"DecimalViewer: Subject {subject.name} has data {subject.data}")


if __name__ == "__main__":

    # 옵저버 관리자
    data1 = Data('Data 1')
    data2 = Data('Data 2')

    # 옵저버
    view1 = DecimalViewer()
    view2 = HexViewer()

    # 옵저버 관리자에게 옵저버 할당
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)

    # 동작
    data1.data = 10
    # DecimalViewer: Subject Data 1 has data 10
    # HexViewer: Subject Data 1 has data 0xa
    data2.data = 15
    # HexViewer: Subject Data 2 has data 0xf
    # DecimalViewer: Subject Data 2 has data 15

    # 옵저버 관리자에 등록되어 있는 옵저버 제거
    data1.detach(view2)
    data2.detach(view2)

    # 동작
    data1.data = 10
    data2.data = 15
    # DecimalViewer: Subject Data 1 has data 10
    # DecimalViewer: Subject Data 2 has data 15

여담

  • Subject의 메소드 notify의 modifier 매개변수를 어떻게 활용할 수 있으려나.
  • 크롬의 다크모드를 예로 들을 수 있겠다.
    • 클라이언트가 '다크모드'를 선택했다.
    • '다크모드'의 옵저버들이 일괄적으로 적절한 스킨으로 선택한다.
  • 발행-구독 패턴과 유사점이 많다.
    • subject가 observer를 보유하는 것이 아닌, 어떤 중간 객체(일반적으로 Queue)를 두어 subject와 observer가 중간 객체를 통해 소통하는 패턴이라고 볼 수 있다.
    • subject와 observer가 서로를 인지할 필요가 없어 결합도가 낮아진다.
    • observer는 필요할 때에만 중간 객체와 소통하여 subject의 변화를 감지하면 된다.
    • 크로스 도메인 상황에서도 구현이 가능해진다.
  • 결론적으로...
    • 특정 객체의 상태 변화 또는 동작이 여러 객체들의 동작 또는 변경을 필요로 할 때 사용될 수 있는 패턴.
    • 리스트와 파이썬 for문 간의 관계를 패턴으로 만든 것이 옵저버 패턴이 아닐까.
Comments