Search

옵저버 패턴 (디자인 패턴)

옵저버 패턴 이해하기

개요

옵저버 디자인 패턴이란?
옵저버 디자인 패턴은 행동 디자인 패턴의 일종으로,
여러 객체가 관찰 중인 다른 객체의 변화를 감지하고 업데이트를 받을 수 있게 합니다.
이는 특정 이벤트에 대해 여러 리스너에게 알리는 알림 시스템과 유사합니다.
옵저버 패턴의 사용 시기 옵저버 패턴은 다음과 같은 상황에서 효과적이라고 합니다
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)
공통점: 두 패턴 모두 컴포넌트 간의 간접적인 통신을 촉진합니다.
차이점:
중재자 패턴: 컴포넌트 간의 직접적인 연결을 제거하고, 단일 중재자를 중심점으로 설정합니다.
옵저버 패턴: 수신자의 동적인 구독과 구독 취소를 허용하여 객체 간 더 유연한 관계를 가능하게 합니다.