[컴퓨터공학]/[Design Pattern]

[Design Pattern] 옵저버 패턴 - Python

딥러닝 도전기 2022. 9. 22. 17:22

[Design Pattern] 옵저버 패턴 - Python

 

옵저버 패턴은 가장 단순한 행위 패턴입니다. 행위 패턴은 이름 그대로 객체의 역할에 초점을 두고, 더 큰 기능을 구현하기 위한 객체 간의 상호작용을 중요시합니다.

객체끼리 상호작용을 하지만 느슨하게 결합되어 있습니다. (loose coupling) 

 

옵저버 패턴에서 객체(Subject)는 자식(Observer)의 목록을 저장하며 Subject가 Observer에 정의된 메소드를 호줄할 때마다 옵저버에 알림을 줍니다.

 

유튜브 구독 + 알림설정을 예시로 들어보겠습니다.

유튜브를 구독과 알림설정을 하게 되면 유튜버가 영상을 게시했을 때 알림설정을 한 모든 구독자에게 새 게시물 알림이 전송됩니다.

여기에서 유튜브가 객체(Subject)에 해당되고, 구독자가 자식(Observer)에 해당됩니다.

 

Subject의 상태변화가 있을 때 Observer에게 notify를 하는 패턴이 옵저버패턴 입니다.

 

옵저버 패턴의 목적은 다음과 같습니다.

  • Subject와 Observer 사이에 일대다(1:N) 관계를 형성하고, 객체의 상태를 다른 종속 객체에 자동으로 알린다.
  • Subject의 핵심 부분을 캡슐화 한다.

 

class Subject: # 유튜버
    def __init__(self):
        self.__observers = []
    
    def register(self, observer):
        self.__observers.append(observer)
    
    def notifyAll(self, *args, **kwargs):
        for observer in self.__observers:
            observer.notify(self, *args, **kwargs)
            

class Observer1: #구독자 1
    def __init__(self, subject):
        subject.register(self)
    
    def notify(self, subject, *args):
        print(type(self).__name__, '::Got', args, 'From', subject)
        
        
class Observer2: #구독자 2
    def __init__(self, subject):
        subject.register(self)
        
    def notify(self, subject, *args):
        print(type(self).__name__, '::Got', args, 'From', subject)

subject = Subject()
observer1 = Observer1(subject)
observer2 = Observer2(subject)
subject.notifyAll('notification')

위의 유튜브 예시를 Python으로 구현한 코드입니다.

유튜버인 Subject에 notifyAll() 메소드에서 observer(구독자)에 있는 notify() 메소드를 호출합니다.

각 Observer는 notify() 메소드를 통해 알림 받을 방법을 변경할 수 있습니다.

유튜브 예제의 옵저버 패턴 클래스 다이어그램은 위 그림과 같습니다.

ConcreteObserver는 실제로 구현된(구상) 옵저버를 의미합니다. 위의 예제에서는 구독자에 해당합니다.

 

옵저버 패턴의 구성원의 각 역할은 다음과 같습니다.

  • Subject : 여러 Observer를 관리하며 Observer는 Subject 클래스의 register()와 unregister() 메소드를 호출하여 자신을 등록 및 해제 합니다. 
  • Observer : Subject를 감시하는 객체를 위한 인터페이스를 제공합니다. Subject의 상태를 알 수 있도록 ConcreteObserver가 구현해야 하는 메소드 notify()를 정의합니다.
  • ConcreteObserver : Subject의 상태를 저장합니다. Subject에 대한 정보와 실제 상태를 일관되게 유지하기 위해 Observer인터페이스를 구현합니다.

 


옵저퍼 패턴 예시

실시간으로 뉴스를 전달하는 뉴스 에이전시를 옵저버 패턴으로 구현해보겠습니다. 기술이 발전하면서 구독자는 단순히 신문이 아닌 이메일과 문자, 음성 메시지 등 다양한 방식으로 뉴스를 전달받습니다. 이를 고려하며 설계해야합니다. (Open-Closed Principle)

 

  • Subject - 뉴스 게시자
Subject의 행동을 NewsPublisher 클래스에 구현한다.
NewsPublisher는 구독자가 구현할 인터페이스를 제공한다.
Observer는 attach() 메소드를 통해 등록하며 detach()를 통해 등록 해제한다.
subscribers()는 Subject에 등록된 구독자 목록을 반환한다.
notifySubscriber()는 NewsPublisher에 등록된 모든 구독자에게 알림을 보낸다.
뉴스 게시자는 addNews() 메소드로 새로운 뉴스를 등록하고 getNews()로 최신 뉴스를 확인한 뒤 Observer에 전달한다.

NewsPublisher Class

class NewsPublisher:
    def __init__(self):
        self.__subscribers = []
        self.__latestNews = None
        
    def attach(self, subscriber):
        self.__subscribers.append(subscriber)
    
    def detach(self):
        return self.__subscribers.pop()
    
    def notifySubscriber(self):
        for sub in self.__subscribers:
            sub.update()
    
    def addNews(self, news):
        self.__latestNews = news
    
    def getNews(self):
        return "Got News : ", self.__latestNews

 

  • Observer 

Subscriber Class

Subscriber는 옵저버이다. 모든 ConcreteObserver의 추상 기본 클래스 이다. (자바에서는 Interface로 합니다.)
Subscriber에는 ConcreteObserver가 구현해야 하는 update()메소드가 있다.
ConcreteObserver는 updata()를 구현해 Subject로 부터 새로운 뉴스 알림을 받는다.
from abc import ABCMeta, abstractmethod

class Subscriber(metaclass=ABCMeta):
    
    @abstractmethod
    def update(self):
        pass

abc(Abstract Base Class)를 import하여 update()를 추상 메서드로 정의합니다.

 

 

  • ConcreteObserver 

SMSSubscriber, EmailSubscriber, AnyOtherSubscriber

 

EmailSubscriber와 SMSSubscriber는 Subscriber 인터페이스를 구현하는 옵저버입니다.
ConcreteObserver의 __init__()메소드는 attach() 메소드를 통해 자신을 NewsPublisher에 등록합니다.
NewsPublisher는 내부적으로 ConcreteObserver의 update() 메소드를 호출해 새로운 뉴스를 알립니다.
class SMSSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)
    
    def update(self):
        print(type(self).__name__, self.publisher.getNews())
        

class EmailSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)
    
    def update(self):
        print(type(self).__name__, self.publisher.getNews())
        
class OtherSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)
    
    def update(self):
        print(type(self).__name__, self.publisher.getNews())

 

마지막으로 main 부분입니다.

news_publisher = NewsPublisher()
for subs in [SMSSubscriber, EmailSubscriber, OtherSubscriber]:
    subs(news_publisher)

print("\nSubscribers : ", news_publisher.subscribers())

news_publisher.addNews("Hello world!")
news_publisher.notifySubscriber()



print("\nDetached:", type(news_publisher.detach()).__name__)
print("\nSubscribers :", news_publisher.subscribers())

 

우선 NewsPublisher 클래스를 통해 객체를 생성한 후 ['SMSSubscriber', 'EmailSubscriber', 'OtherSubscriber']를  news_publisher에 등록한 후 news publisher에 등록되어있는 구독자를 확인합니다.

news_publisher = NewsPublisher()
for subs in [SMSSubscriber, EmailSubscriber, OtherSubscriber]:
    subs(news_publisher)

print("\nSubscribers : ", news_publisher.subscribers())
>> Subscribers :  ['SMSSubscriber', 'EmailSubscriber', 'OtherSubscriber']

 

다음으로 "Hello World"라는 뉴스를 addNews() 메소드로 전달합니다.

뉴스 전달 후 구독자들에게 notifySubscriber() 메소드를 통해 알림을 줍니다.

notifySubscriber() 메소드를 호출하면 모든 구독자들이 update() 메소드를 호출합니다.

 

def notifySubscriber(self):
    for sub in self.__subscribers:
        sub.update()
def update(self):
    print(type(self).__name__, self.publisher.getNews())
>> SMSSubscriber ('Got News : ', 'Hello world!')
>> EmailSubscriber ('Got News : ', 'Hello world!')
>> OtherSubscriber ('Got News : ', 'Hello world!')

 

마지막으로 news_publisher.detach() 메소드를 호출하여 마지막에 추가된 구독자를 pop해서 삭제합니다.

그 후 구독자를 확인하면 다음과 같습니다.

print("\nDetached:", type(news_publisher.detach()).__name__)
print("\nSubscribers :", news_publisher.subscribers())
>> Detached: OtherSubscriber

>> Subscribers : ['SMSSubscriber', 'EmailSubscriber']

 

반응형