Exist Technote

[Python] State Pattern (상태 패턴) 본문

Design Pattern/Behavioral

[Python] State Pattern (상태 패턴)

by_Exist 2020. 10. 16. 21:12

(AI야! 동작해! 네 상태에 따라!) https://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

State Pattern이란?

  • 객체 내부의 상태에 따라 스스로 행동을 변경할 수 있게끔 허가하는 패턴.
  • 마치 자신의 클래스를 바꾸는 것처럼 보이게 된다.

예제

  • 변경 전의 라디오 클래스
class Radio:

    STATIONS = {
        "AM": ["1250", "1380", "1510"],
        "FM": ["81.3", "89.1", "103.9"]
    }

    def __init__(self):
        self.modulation = "AM"
        self.stations_point = 0

    def toggle(self):
        if self.modulation == "AM":
            self.modulation = "FM"
            print("FM으로 전환")
        else:
            self.modulation == "AM"
            print("AM으로 전환")

    def scan(self):
        self.stations_point += 1
        if self.stations_point >= len(self.STATIONS[self.modulation]):
            self.stations_point = 0
        station = self.STATIONS[self.modulation][self.stations_point]
        print(f"[{self.modulation}] 스캔중... {station}")


if __name__ == "__main__":

    radio = Radio()

    actions = [radio.scan] * 2 + [radio.toggle] + [radio.scan] * 2
    actions *= 2

    for action in actions:
        action()

# 1. toggle, scan 등의 메소드 동작 과정이 분명하고 명확하게 드러나지 않는다.
# 2. 만일 새로운 modulation 주파수 변조 방식이 등록될 경우 기존 코드를 반드시 변경해야 한다.
# 3. STATIONS의 길이가 다를 경우 scan의 조건문을 수정해야 한다.
# 주파수 변조 방식의 상태에 따라 사용되는 객체를 변경하며 사용할 것이다.
# 어떤 주파수 변조 방식에도 공통적으로 사용되는 부분을 슈퍼 클래스에 정의한다.
class Modulation:

    def scan(self):
        self.pos += 1
        if self.pos == len(self.stations):
            self.pos = 0
        print("[{}] 스캔중... {}".format(self.name, self.stations[self.pos]))


# 달리 동작하는 부분은 서브클래스에서 정의한다.
class AmModulation(Modulation):
    def __init__(self, radio):
        self.radio = radio
        self.stations = ["1250", "1380", "1510"]
        self.pos = 0
        self.name = "AM"

    def toggle(self):
        print("FM으로 전환")
        self.radio.state = self.radio.fm_modulation


class FmModulation(Modulation):
    def __init__(self, radio):
        self.radio = radio
        self.stations = ["81.3", "89.1", "103.9"]
        self.pos = 0
        self.name = "FM"

    def toggle(self):
        print("AM으로 전환")
        self.radio.state = self.radio.am_modulation


class Radio:

    def __init__(self):
        # 각 주파수 변조 방식(상태) has방식으로 보유하도록 설계.
        self.am_modulation = AmModulation(self)
        self.fm_modulation = FmModulation(self)
        self.modulation = self.am_modulation

    # self.modulation에 am_modulation이나 fm_modulation 둘중 무엇이 오더라도
    # 공통적으로 사용될 수 있는 두 메서드.
    def toggle(self):
        self.modulation.toggle()

    def scan(self):
        self.modulation.scan()



if __name__ == "__main__":

    radio = Radio()

    actions = [radio.scan] * 2 + [radio.toggle] + [radio.scan] * 2
    actions *= 2

    for action in actions:
        action()

# 라디오와 주파수 상태가 분리되어 새로운 modulation이 등록되더라도 쉽게 관리할 수 있다.
# 로직의 흐름을 분명하게 파악할 수 있다.
# 불필요한 조건문을 나열하지 않아도 된다.
# 새로 기능을 추가할 때, 그것이 라디오의 범위인지 변조 방식의 범위인지 명확하게 구분할 수 있다.

여담

  • 엘레베이터의 여러 조건(내려가는 중, 버튼이 어디서 눌렸다 등등)의 상황에서 동작을 변경해야 할 경우 사용할 수 있겠다.
  • "유한상태기계"와 관련이 깊다. 자세한 정보는 이곳에서 확인할 수 있다.
    • 가질수 있는 상태가 한정되며, 한번에 하나의 상태만 될 수 있고, 각 상태는 다른 이벤트를 지니며, 다음 상태로 변경되는 전이 조건을 지녀야 한다는 의미이다.
  • "객체 내부의 상태에 따라 스스로 행동을 변경할 수 있게끔 허가하는 패턴"의 의미를 다음과 같이 풀어낼 수 있을 듯 하다.
    • 알고리즘이 유한상태기계(쉽게 말해, 순환 순서도)로 정리될 수 있을 때 각 상태를 객체화(동일한 인터페이스, 상태에 따른 동작, 다음번 상태로 전이할 수 있는 메소드 정의)하여 사용하는 패턴.
Comments