디자인 패턴 - 행동 패턴 Behavior Pattern

밀루·2023년 12월 5일
0

괴발개발 개발일지

목록 보기
25/26
post-thumbnail

행동 패턴이란

Observer Pattern

어떨 때 쓸까?

일대다 관계에서 요긴하게 쓰이는 패턴으로 하나의 subject와 다수의 observers들이 있다. observer와 subject간 느슨한 연결을 통해 subject의 상태가 바뀌면, subject와 observers간 강한 연결 없이도 subject의 상태가 바뀌었음을 알릴 수 있다.
이런 특징으로 인해, 한 객체의 상태가 바뀌었을때 다른 여러 객체들이 이 변화를 감지해야 할 경우 아주 요긴하게 사용한다. 객체 상태가 빈번하게 바뀌는 유저 인터페이스, 알림 시스템, 이벤트 트리거 시스템에서 쓸 수 있다.

  1. 주식 시장 업데이트: 거래 어플리케이션에서 주식 종목의 가격이 변하면 트레이더들에게 알림이 가야한다.
  2. 온도 감지 시스템: 온도가 바뀌면 에어컨을 키거나 보일러를 틀어야 하는 경우, 온도 변화에 영향을 받는 다른 객체들이 observers, 그리고 온도가 subject가 된다.
  3. 채팅 앱: 채팅방에 새 메시지가 오면 이 채팅방에 속한 다른 유저들에게도 알림이 온다. 이런 경우 subject는 채팅방, observer는 그 방에 속한 유저가 된다.

여기서 잠깐, 약한 결합(loosed coupled) 이 뭐지?
일반적으로 이들 사이에 데이터 공유, 참조 없이,메세지만 갖고 통신을 하는 것을 loosely coupled 를 달성한 것으로 간주한다.
Observer 패턴은 생산/구독자 간의 일관된 인터페이스를 통한 메세지 전파를 다루는 디자인 패턴이므로 이는 loosely coupled 를 달성하는 것으로 볼 수 있다

참고: https://m.blog.naver.com/gracefulife/221394894366

예제 코드

class Observable:
    def __init__(self):
        self.observers = [] # 클래스 init시 옵저버 리스트 생성

    def add_observer(self, observer): # 옵저버를 옵저버 리스트에 추가
        if observer not in self.observers:
            self.observers.append(observer)

    def remove_observer(self, observer): # 옵저버를 옵저버 리스트에서 제거
        if observer in self.observers:
            self.observers.remove(observer)

    def notify_observers(self, *args, **kwargs): # 대상의 상태가 바뀔 시 옵저버 객체들에게 알리기
        for observer in self.observers:
            observer.update(self, *args, **kwargs)

class Observer:
    def update(self, observable, *args, **kwargs): # 관찰 대상이 바뀌고 이에 대한 알림이 오면 옵저버 상태를 업데이트함
        pass

class WeatherStation(Observable): # 관찰되는 객체
    def set_temperature(self, temperature):
        self.temperature = temperature
        self.notify_observers() # 온도가 바뀌고 옵저버들에게 알림을 줌

class PhoneDisplay(Observer): # 관찰하는 객체
    def update(self, observable, *args, **kwargs):
        if isinstance(observable, WeatherStation):
            temperature = observable.temperature
            print(f"Temperature is {temperature} degrees Celsius - Phone")
            
class ComputerDisplay(Observer): # 관찰하는 객체
    def update(self, observable, *args, **kwargs):
        if isinstance(observable, WeatherStation):
            temperature = observable.temperature
            print(f"Temperature is {temperature} degrees Celsius - Computer")
            
weather_station = WeatherStation()
phone_display = PhoneDisplay()
computer_display = ComputerDisplay()

weather_station.add_observer(phone_display)
weather_station.add_observer(computer_display)
weather_station.set_temperature(25)

결과

Temperature is 25 degrees Celsius - Phone
Temperature is 25 degrees Celsius - Computer

장단점

장점:

  • 객체간의 느슨한 결합을 통해 유지보수가 쉽다.
  • 확장성이 용이하다. 더 많은 옵저버 객체가 추가되어도 그 옵저버들은 subject의 상태가 변화하면 알림을 받는다.
  • 코드 재사용성이 증가한다. observer-subject간 관계는 재사용하기 쉽다.

단점:

  • Subject는 Observer 리스트를 늘 가지고 있어야 한다. 따라서 Observer 객체가 삭제되면
  • Subject의 여러 요소가 변화할 시 이를 구현하는 코드가 복잡해 질 수 있다.

State Pattern

어떨 때 쓸까?

객체의 내부 상태가 변할때 객체의 동작(행동)이 바뀌어야 하는 경우 사용한다. 이 패턴에서는 객체의 상태를 각각 고려한다. 각 상태를 나타내는 별도의 클래스가 있고, 이 클래스에서 동작을 수행한다. 클래스의 공통 동작은 부모 클래스에서 추상화할 수 있다.

가령 문이라는 객체가 있고, 이 문이 닫힌 상태일때와 열린 상태일때 동작이 달라진다고 생각해보자. 닫힌 상태에서는 문을 열 수 있고, 열린 상태에서는 문을 닫을 수 있다.

예제 코드

from abc import ABC, abstractmethod

class DoorState(ABC): # State를 나타내는 parent class
    
    @abstractmethod
    def open(self):
        pass

    @abstractmethod
    def close(self):
        pass

class OpenState(DoorState):
# DoorState를 abstract pattern을 이용해 상속받아 구체화하는 자식 클래스 - 닫힌 상태를 나타냄
    def open(self):
        print("The door is already open.")
        return self

    def close(self):
        print("Closing the door...")
        # Perform door closing process
        return ClosedState()

class ClosedState(DoorState):
# DoorState를 abstract pattern을 이용해 상속받아 구체화하는 자식 클래스 - 열린 상태를 나타냄
    def open(self):
        print("Opening the door...")
        # Perform door opening process
        return OpenState()

    def close(self):
        print("The door is already closed.")
        return self

class Door:
    def __init__(self):
        self.current_state = ClosedState() # 초기화때 Close State를 먼저 상속받음 (닫힌 상태로 시작)

    def change_state(self, state): # state class를 상속받아 현 객체의 상태를 바꿈
        self.current_state = state

    def open(self): # current_state를 OpenState로 바꿈
        self.change_state(self.current_state.open())

    def close(self): # current_state를 CloseState로 바꿈
        self.change_state(self.current_state.close())

# Example usage
door = Door()

door.open() #Opening the door...
door.close() #Closing the door...
door.close() # Output: "The door is already closed."
door.open() # Output: "Opening the door..."
door.open() # Output: "The door is already open."
door.close() # Output: "Closing the door..."

위 코드는 DoorState 라는 인터페이스가 있다. 이 인터페이스에서 OpenState와 CloseState라는 두 가지 상태를 나타내는 구체적인 클래스를 만들었다. 그리고 이 상태가 적용될 Door라는 클래스가 있다. 이 클래스는 OpenState 객체 혹은 CloseState 객체를 가지는데, 두 State를 나타내는 클래스 안에 각 상태에서 수행할 함수가 있다.

장단점

장점:

  • if-else문을 써야하는 상황을 대체할 수 있다. 따라서 여러가지 상태별 다른 함수를 구현해야 하는 경우, 복잡한 if-else문을 대체할 수 있다.

단점:

  • State가 많아지면 많아질수록 각 상태와 상태간 전환시 사용할 함수들을 구현해야한다.

참고

옵저버 패턴
https://www.geeksforgeeks.org/observer-method-python-design-patterns/

https://medium.com/@endlichfelipe/implementing-the-observer-design-pattern-in-python-e1201e32d1f1

스테이트 패턴
스테이트 패턴 설명 참고2

profile
벨로그에 틀린 코드나 개선할 내용이 있을 수 있습니다. 지적은 언제나 환영합니다.

0개의 댓글