2. 옵저버 패턴
책을 읽으면서 정확히 이해하지는 못했지만 이해한만큼 써보려고 한다. 일단 Pull방식은 배제하고 Push 방식에 대해서만 서술한다.
먼저 옵저버 패턴을 통해서 객체 사이를 느슨하게 연결할 수 있다고 한다. 객체간의 연결이 뭔지는 둘째치더라도, 보통 강하게 결합되어 있는게 좋은 거 아닌가? 라고 싶지만 왜 느슨한 결합이 좋은지는 다음과 같은 예시를 보면 알 수 있다.
class EmailNotifier:
def send_email(self, message):
print(f"Sending email: {message}")
class SMSNotifier:
def send_sms(self, message):
print(f"Sending SMS: {message}")
class NotificationManager:
def __init__(self):
self.email_notifier = EmailNotifier()
self.sms_notifier = SMSNotifier()
def notify(self, message):
self.email_notifier.send_email(message)
self.sms_notifier.send_sms(message)
notification_manager = NotificationManager()
notification_manager.notify("Hello, this is a notification!")
위 예시는 알림을 해주는 간단한 코드이다. 알림 매니저를 통해 이메일 혹은 문자메세지로 알림 메세지를 보내는 것을 간단하게 구현한 예시이다. 이 코드에서는 알림 매니저에서 Class가 직접적으로 참조되어 있는 것을 확인할 수 있는데, 이런 경우가 객체 사이가 강하게 결합된 예시다. 덕분에 알림을 보내야하는 주체가 늘어나거나 해서 변경사항이 필요하면 NotificationManager 역시도 수정 해야된다.
from abc import ABC, abstractmethod
# 인터페이스 정의
class Notifier(ABC):
@abstractmethod
def send(self, message):
pass
class EmailNotifier(Notifier):
def send(self, message):
print(f"Sending email: {message}")
class SMSNotifier(Notifier):
def send(self, message):
print(f"Sending SMS: {message}")
class NotificationManager:
def __init__(self):
self.notifiers = []
def register_notifier(self, notifier):
self.notifiers.append(notifier)
def notify(self, message):
for notifier in self.notifiers:
notifier.send(message)
notification_manager = NotificationManager()
email_notifier = EmailNotifier()
sms_notifier = SMSNotifier()
notification_manager.register_notifier(email_notifier)
notification_manager.register_notifier(sms_notifier)
notification_manager.notify("Hello, this is a notification!")
느슨한 결합의 예시다. 덧붙이면 python에서는 추상클래스를 통해 느슨한 결합을 구현하기 좋은것 같다. 위 코드에서는 Notification Manager에는 Notifier 객체를 담는 것과 Notifier 객체를 순회하면서 send()함수를 호출해주는 것 밖에 없다. 따라서 NotificationManager는 Notifier 객체가 추가되든 수정되어야하든 아무런 상관이 없다!
위의 예시는 느슨한 구조의 python 예제이다. 옵저버 패턴은 느슨한 구현 위에 관찰 대상과 옵저버 사이의 상호관계가 두드러지게 구성한 것이기에, 예시를 보았으니 옵저버 패턴의 장점을 쉽게 이해할 수 있을거라 생각한다. 이제 옵저버 패턴의 예시와 함께 장점과 구조에 대해서 정리하면 다음과 같다.
from abc import ABC, abstractmethod
# Observer 인터페이스
class Observer(ABC):
@abstractmethod
def update(self, temperature):
pass
# Subject 클래스
class TemperatureSensor:
def __init__(self):
self.observers = []
def register_observer(self, observer):
self.observers.append(observer)
def unregister_observer(self, observer):
self.observers.remove(observer)
def notify_observers(self, temperature):
for observer in self.observers:
observer.update(temperature)
def set_temperature(self, temperature):
print(f"TemperatureSensor: new temperature is {temperature}")
self.notify_observers(temperature)
# 구체적인 Observer 클래스
class TemperatureDisplay(Observer):
def update(self, temperature):
print(f"TemperatureDisplay: current temperature is {temperature}")
class TemperatureLogger(Observer):
def update(self, temperature):
print(f"TemperatureLogger: logging temperature {temperature}")
# 클라이언트 코드
sensor = TemperatureSensor()
display = TemperatureDisplay()
logger = TemperatureLogger()
# 옵저버 등록
sensor.register_observer(display)
sensor.register_observer(logger)
# 새로운 온도 설정 및 옵저버에게 알림
sensor.set_temperature(25)
# 옵저버 제거
sensor.unregister_observer(display)
sensor.unregister_observer(logger)
# 새로운 온도 설정 및 옵저버에게 알림
sensor.set_temperature(30)
느슨한 결합 예시와 거의 비슷한 구성이다. 예제에서는 TemperatureSensor가 주체(Subject) 역할을 하고, 옵저버인 TemperatureDisplay와 TemperatureLogger가 Observer 인터페이스를 구현하여 온도 변경 알림을 받는다. 새로운 온도가 설정되면, 주체는 등록된 모든 옵저버에게 알림을 보내어 되어 주제 클래스의 수정이 없어도 추가적인 Observer 클래스들을 생성하더라도 문제가 없다.
정리하자면, 옵저버 패턴의 기본 구조 이 두 구성을 따른다.
- 관찰 대상(Subject): 옵저버에게 상태 변화를 알리는 객체로, 옵저버를 등록, 해제, 알림하는 메서드를 포함한다.
- 옵저버(Observer): 관찰 대상의 상태 변화를 수신하고 처리하는 객체로, 관찰 대상이 발생한 변화에 따라 특정 작업을 수행하는 메서드를 포함한다.
그리하여 옵저버 패턴은 다음과 같은 장점을 가진다.
- 확장성: 옵저버와 관찰 대상(subject) 간의 관계가 느슨하게 연결되어 있어, 새로운 옵저버를 쉽게 추가하거나 기존 옵저버를 제거할 수 있다.
- 유지 보수성: 옵저버와 관찰 대상이 서로 독립적으로 변경되고 발전할 수 있다. 이로 인해 각 구성 요소의 수정이 다른 요소에 영향을 주지 않는다.
고로 옵저버 패턴을 사용하면, 관찰 대상과 옵저버 사이의 관계를 느슨하게 유지할 수 있어, 두 객체가 독립적으로 발전하고 변경될 수 있으며, 확장성과 유지 보수성이 향상된다.