프로그래밍/CS

[디자인패턴] Observer Pattern (옵저버 패턴)

choar 2022. 8. 5. 17:35
반응형

Observer Pattern

Observer Pattern(옵저버 패턴)이란 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.

이 패턴의 핵심은 옵저버 또는 리스너라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록시킨다. 그리고 각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리한다.

옵저버 패턴에는 2가지 역할이 있다. 하나는 subject, 다른 하나는 observer이다.

subject는 이벤트를 발생시키는 주체로, 일을 수행하고(ex. 회원 가입한 유저를 DB에 등록) 일어난 변화를 observer들에게 알린다.

observer는 발생한 이벤트를 받아 처리한다. observer는 listener라고도 한다.

옵저버 패턴

 

Example

ArjanCodes라는 유튜버의 유튜브 영상 및 GitHub를 바탕으로 정리했다. (하단 References 참고)

user가 가입할 수 있는 서비스의 백엔드 api를 짜는 상황이라고 가정하자.

 

수정 전 api/user.py를 살펴보자.

from lib.email import send_email
from lib.db import create_user, find_user
from lib.log import log
from lib.slack import post_slack_message
from lib.stringtools import get_random_string

def register_new_user(name: str, password: str, email: str):
    # create an entry in the database
    user = create_user(name, password, email)
    
    # post a Slack message to sales department
    post_slack_message("sales",
        f"{user.name} has registered with email address {user.email}. Please spam this person incessantly.")

    # send a welcome email
    send_email(user.name, user.email,
        "Welcome",
        f"Thanks for registering, {user.name}!\nRegards, The DevNotes team")

    # write server log
    log(f"User registered with email address {user.email}")

register_new_user라는 함수에서

(1) user를 DB에 등록하고

(2) slack 메세지에 구독시키고

(3) 환영 이메일을 보내고

(4) 서버 로그를 작성하는 일을 모두 하고 있다.

코드가 길고 가독성이 떨어진다. 유지보수하기도 어려울 것이다.

 

일단 api_v2/event.py를 만든다. 이 부분이 핵심이다.

subscribers = dict()

def subscribe(event_type: str, fn):
    if not event_type in subscribers:
        subscribers[event_type] = []
    subscribers[event_type].append(fn)

def post_event(event_type: str, data):
    if not event_type in subscribers:
        return
    for fn in subscribers[event_type]:
        fn(data)

 

subscribe(event_type, fn) 함수는 subscribers 딕셔너리에 event_type이라는 key가 없으면 해당 key: [] (빈 배열) 쌍을 추가하고, 그 배열에 fn 함수를 추가해준다.

post_event(event_type, data) 함수는 subscribers 딕셔너리에서 event_type key의 value를 전부 실행해준다. 즉, event_type이 subscribe한 함수를 전부 실행해준다. 이때 함수에 매개변수가 data로 들어간다.

(좌) subscribe (우) post_event

 

다음으로 api_v2 폴더 내에 'listener'로 끝나는 코드를 살펴보자.

# api_v2/slack_listener.py

from lib.slack import post_slack_message
from .event import subscribe

def handle_user_registered_event(user):
    post_slack_message("sales",
        f"{user.name} has registered with email address {user.email}. Please spam this person incessantly.")

def handle_user_upgrade_plan_event(user):
    post_slack_message("sales",
        f"{user.name} has upgraded their plan.")

def setup_slack_event_handlers():
    subscribe("user_registered", handle_user_registered_event)
    subscribe("user_upgrade_plan", handle_user_upgrade_plan_event)

handle_user_registered_event 함수는 user가 가입했음을 slack 메세지로 알린다.

handle_user_upgrade_plan_event 함수는 user가 plan을 업그레이드했음을 slack 메시지로 알린다.

setup_slack_event_handlers 함수는 subscribers의 (1) "user_registered"라는 event type에 handle_user_registered_event 함수를 등록시키고, (2) "user_upgrade_plan"이라는 event type에 handle_user_upgrade_plan_event 함수를 등록시킨다.

email_listener.py, log_listener.py도 비슷한 구성이다. setup 함수를 실행시키면 subscribe 함수가 실행됨을 기억하자.

 

수정 후 api_v2/user.py 코드를 살펴보자.

from lib.db import create_user, find_user
from lib.stringtools import get_random_string
from .event import post_event

def register_new_user(name: str, password: str, email: str):
    # create an entry in the database
    user = create_user(name, password, email)

    # post an event
    post_event("user_registered", user)

def password_forgotten(email: str):
    # retrieve the user
    user = find_user(email)

    # generate a password reset code
    user.reset_code = get_random_string(16)

    # post an event
    post_event("user_password_forgotten", user)

def reset_password(code: str, email: str, password: str):
    # retrieve the user
    user = find_user(email)

    # reset the password
    user.reset_password(code, password)

register_new_user 함수에서 user를 만들고 이후 수행됐던 모든 일들이 post_event("user_registered", user)로 대체된 것을 볼 수 있다. 코드의 가독성이 훨씬 좋아졌다.

 

수정 후 observer-after.py 코드를 살펴보자.

from api_v2.slack_listener import setup_slack_event_handlers
from api_v2.log_listener import setup_log_event_handlers
from api_v2.email_listener import setup_email_event_handlers

from api_v2.user import register_new_user, password_forgotten
from api_v2.plan import upgrade_plan

# initialize the event structure
setup_slack_event_handlers()
setup_log_event_handlers()
setup_email_event_handlers()

# register a new user
register_new_user("Arjan", "BestPasswordEva", "hi@arjanegges.com")

# send a password reset message
password_forgotten("hi@arjanegges.com")

# upgrade the plan
upgrade_plan("hi@arjanegges.com")

event handler들을 setup해주는 코드만 추가된 것을 확인할 수 있다.

옵저버 패턴은 조금 이해하기 까다로웠는데, 이 패턴을 잘 활용하면 강력한 효과를 발휘할 수 있을 것 같다.

 

🌟 내용에 오류가 있다면 댓글 달아주시면 감사하겠습니다.


References

https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4

https://www.youtube.com/watch?v=oNalXg67XEE&t=749s 

https://github.com/ArjanCodes/betterpython/tree/main/4%20-%20observer%20pattern

반응형