옵저버 패턴 이해하기
개요
옵저버 디자인 패턴이란?
옵저버 디자인 패턴은 행동 디자인 패턴의 일종으로,
여러 객체가 관찰 중인 다른 객체의 변화를 감지하고 업데이트를 받을 수 있게 합니다.
이는 특정 이벤트에 대해 여러 리스너에게 알리는 알림 시스템과 유사합니다.
옵저버 패턴의 사용 시기
옵저버 패턴은 다음과 같은 상황에서 효과적이라고 합니다
1.
이벤트 기반 아키텍처: 한 객체의 변화로 인해 여러 객체의 업데이트가 필요한 시스템에서 구현합니다.
2.
동적 객체 상호작용: 한 객체의 변화가 다른 객체들의 업데이트를 필요로 할 때, 특히 정확한 객체 집합이 미리 정해지지 않거나 동적으로 변할 때 사용합니다.
3.
제한적 또는 특정 관찰: 특정 객체들이 다른 객체를 제한된 기간 동안 또는 특정 조건에서 관찰해야 할 때 사용합니다.
4.
동적 구독: 객체들이 필요에 따라 구독 목록에 참여하거나 떠날 수 있는 동적 구독 목록을 활용할 때 사용합니다.
용어와 핵심 구성 요소
옵저버 패턴의 기본 구성 요소를 이해하는 것은 매우 중요합니다.
참조
•
게시자(Publisher)
◦
옵저버블(Observable, subject)이 여기에 해당합니다.
◦
옵저버블은 게시자의 역할을 수행합니다.
▪
주요 특징:
•
사건(이벤트)을 발생시키는 주체입니다.
•
이벤트의 생성자 역할을 합니다.
•
자신의 상태 변화나 특정 행동에 의해 이벤트를 트리거합니다.
•
구독자(옵저버)들을 관리하기 위한 인프라를 포함합니다.
◦
Observable과 Event Bus는 비슷한 역할을 수행
차이점과 유사점
•
구독자 인터페이스(Subscriber Interface)
◦
이 부분은 옵저버(Observer)가 구현해야 하는 인터페이스를 정의합니다.
◦
주요 특징:
▪
알림을 받기 위한 인터페이스를 선언합니다.
▪
일반적으로 update() 같은 메서드를 포함하여 게시자로부터 이벤트 정보를 받습니다.
•
구체적 구독자(Concrete Subscribers)
◦
옵저버(Observer)가 여기에 해당합니다.
◦
주요 특징:
▪
시스템에서 발생하는 특정 사건에 대해 알림을 받고자 하는 객체입니다.
▪
구독자 인터페이스를 실제로 구현합니다.
▪
게시자(옵저버블)가 발행한 알림에 대응하여 특정 동작을 수행합니다.
▪
이벤트를 소비하고, 알림을 받아 적절한 조치를 취합니다.
•
클라이언트(Client)
◦
옵저버블(게시자)과 옵저버(구독자)를 사용하는 코드입니다.
◦
주요 역할:
▪
게시자(옵저버블)와 구독자(옵저버)의 인스턴스를 독립적으로 생성합니다.
▪
구독자(옵저버)를 게시자(옵저버블)에 등록하여 업데이트를 받을 수 있게 합니다.
1단계: Subject (주체)
옵저버들의 목록을 관리하고 변경 사항을 알립니다
from abc import ABC, abstractmethod
class Subject(ABC):
def __init__(self):
self._observers = set()
def attach(self, observer):
"""옵저버를 주체의 목록에 추가합니다."""
self._observers.add(observer)
def detach(self, observer):
"""옵저버를 주체의 목록에서 제거합니다."""
self._observers.remove(observer)
def notify_observers(self):
"""연결된 모든 옵저버에게 알립니다."""
for observer in self._observers:
observer.update()
Python
복사
2단계: Observer 인터페이스
업데이트를 받기 위한 메서드를 선언하여 옵저버들 간의 일관성을 보장합니다.
class Observer(ABC):
@abstractmethod
def update(self):
"""업데이트를 받기 위한 추상 메서드입니다."""
pass
Python
복사
3단계: 구체적 옵저버
받은 업데이트를 처리하기 위해 update 메서드를 구현합니다.
class ConcreteObserver(Observer):
def __init__(self, name):
self.name = name
def update(self):
"""알림을 받고 출력합니다."""
print(f"{self.name}가 알림을 받았습니다.")
Python
복사
4단계: 클라이언트
주체와 옵저버들을 생성하고, 옵저버들을 주체에 연결한 후, 메시지로 옵저버들에게 알립니다.
if __name__ == "__main__":
# 주체 생성
subject = Subject()
# 옵저버들 생성
observer1 = ConcreteObserver("옵저버1")
observer2 = ConcreteObserver("옵저버2")
observer3 = ConcreteObserver("옵저버3")
# 옵저버들을 주체에 연결
subject.attach(observer1)
subject.attach(observer2)
subject.attach(observer3)
# 옵저버들에게 알림
subject.notify_observers()
Python
복사
실제로 옵저버 패턴을 사용하는 방법을 보여줍니다.
주체와 옵저버들을 생성하고, 옵저버들을 주체에 연결한 후, 주체가 모든 옵저버에게 알림을 보내는 과정을 구현합니다.
예시: 채팅 애플리케이션
옵저버 패턴의 효과와 기능을 보여주기 위해, Python을 사용하여 간단한 채팅 애플리케이션을 구현합니다.
이 채팅 애플리케이션에서 사용자들은 옵저버 역할을 합니다.
한 사용자가 메시지를 보낼 때마다 채팅방의 다른 모든 사용자들(옵저버들)이 이 메시지를 받아 표시합니다.
1단계: ChatRoom - 게시자(Publisher)
채팅방 참가자들을 관리하고 모든 참가자에게 메시지를 브로드캐스트합니다.
from abc import ABC, abstractmethod
class ChatRoom:
def __init__(self):
self._participants = set()
def join(self, participant):
"""새로운 참가자를 채팅방에 추가합니다."""
self._participants.add(participant)
def leave(self, participant):
"""채팅방에서 참가자를 제거합니다."""
self._participants.remove(participant)
def broadcast(self, message):
"""채팅방의 모든 참가자에게 메시지를 보냅니다."""
for participant in self._participants:
participant.receive(message)
Python
복사
이 클래스는 채팅방을 나타내며, 참가자들을 관리하고 메시지를 모든 참가자에게 전달하는 역할을 합니다.
2단계: Participant - 구독자 인터페이스(Subscriber Interface)
메시지를 받기 위한 메서드를 정의하여 채팅 멤버들 간의 일관성을 보장합니다.
class Participant(ABC):
@abstractmethod
def receive(self, message):
"""메시지를 받기 위한 추상 메서드입니다."""
pass
Python
복사
이 인터페이스는 모든 구체적인 채팅 멤버 클래스가 구현해야 하는 receive 메서드를 정의합니다.
3단계: ChatMember - 구체적 구독자(Concrete Subscribers)
받은 메시지를 표시하기 위해 receive 메서드를 구현합니다.
class ChatMember(Participant):
def __init__(self, name):
self.name = name
def receive(self, message):
"""메시지를 받고 표시합니다."""
print(f"{self.name}이(가) 받은 메시지: {message}")
Python
복사
이 클래스는 Participant 인터페이스를 구현하며, 실제로 메시지를 받았을 때 수행할 동작을 정의합니다.
4단계: 클라이언트(Client)
채팅방과 채팅 멤버들을 생성하고, 채팅 멤버들을 채팅방에 연결한 후, 환영 메시지를 채팅방에 보냅니다.
if __name__ == "__main__":
# 채팅방 생성
general_chat = ChatRoom()
# 참가자들 생성
user1 = ChatMember("사용자1")
user2 = ChatMember("사용자2")
user3 = ChatMember("사용자3")
# 참가자들이 채팅방에 참여
general_chat.join(user1)
general_chat.join(user2)
general_chat.join(user3)
# 채팅방에 메시지 보내기
general_chat.broadcast("채팅방에 오신 것을 환영합니다!")
Python
복사
Python 프로젝트에서 옵저버 패턴의 사용
1.
기본 옵저버 패턴 구현:
•
주체(Subject)가 옵저버들의 리스트를 유지하고, 상태 변경 시 이들에게 알림을 보냅니다.
•
옵저버들은 주체의 변화에 반응하여 추가 작업을 수행합니다.
•
위에 채팅 프로그램이 예제
2.
Django에서의 구현 (시그널 시스템)
•
Django는 시그널이라는 개념으로 옵저버 패턴을 구현합니다.
•
시그널은 Signal 클래스의 인스턴스로, 옵저버들(여기서는 리시버라고 불림)의 리스트를 관리합니다.
from django.dispatch import Signal
# 시그널 정의
something_happened = Signal()
# 리시버 추가
def handle_the_happening(sender, **kwargs):
print("something happened")
# 리시버를 시그널에 연결
something_happened.connect(handle_the_happening)
# 시그널 발송 (이로 인해 handle_the_happening 호출)
something_happened.send(sender=None)
Python
복사
3.
FastAPI에서의 사용 (비동기 시그널)
•
FastAPI는 비동기 프레임워크이므로, 비동기 시그널 시스템이 필요합니다.
•
async-signals 라이브러리를 사용하여 비동기 옵저버 패턴을 구현할 수 있습니다.
from async_signals import Signal
# 시그널 정의
something_happened = Signal()
# 비동기 리시버 추가
async def handle_the_happening(sender, **kwargs):
print("something happened")
# 리시버를 시그널에 연결
something_happened.connect(handle_the_happening)
# 시그널 비동기 발송
await something_happened.send(sender=None)
Python
복사
옵저버 패턴과 다른 패턴들의 관계
옵저버 패턴과 다른 디자인 패턴들 간의 관계
1.
옵저버 vs. 책임 연쇄 패턴(Chain of Responsibility)
•
공통점: 두 패턴 모두 동적인 정보 또는 요청의 흐름을 다룹니다.
•
차이점:
◦
책임 연쇄 패턴: 요청을 잠재적 수신자들의 체인을 따라 전달합니다.
◦
옵저버 패턴: 구독 기반의 알림을 용이하게 합니다.
2.
옵저버 vs. 커맨드 패턴(Command)
•
공통점: 두 패턴 모두 발신자와 수신자 사이에 일방향 연결을 설정합니다.
•
차이점:
◦
커맨드 패턴: 요청을 객체로 캡슐화하여 요청의 매개변수화와 큐잉을 가능하게 합니다.
◦
옵저버 패턴: 요청을 캡슐화하지 않고 구독 기반의 알림에 중점을 둡니다.
3.
옵저버 vs. 중재자 패턴(Mediator)
•
공통점: 두 패턴 모두 컴포넌트 간의 간접적인 통신을 촉진합니다.
•
차이점:
◦
중재자 패턴: 컴포넌트 간의 직접적인 연결을 제거하고, 단일 중재자를 중심점으로 설정합니다.
◦
옵저버 패턴: 수신자의 동적인 구독과 구독 취소를 허용하여 객체 간 더 유연한 관계를 가능하게 합니다.