15장 상태 패턴
부정적인 압력.
압박을 받고 있는 여왕
소프트웨어 문제를 통해 생각하는 데 매우 유용한 도구는 상태 다이어그램입니다. 상태 다이어그램에서 노드는 시스템의 상태를 나타내고 가장자리는 시스템의 한 노드와 다른 노드 간의 전환인 그래프를 구성합니다. 상태 머신 다이어그램은 특정 입력이 주어진 시스템의 상태에 대해 시각적으로 생각할 수 있게 해주기 때문에 유용합니다. 또한 시스템이 한 상태에서 다음 상태로 전환되는 방식을 고려하도록 안내됩니다.
이 책의 앞부분에서 게임 제작에 대해 언급했으므로 플레이어 캐릭터를 상태 머신으로 모델링할 수 있습니다. 플레이어는 서서 시작할 수 있습니다. 왼쪽 또는 오른쪽 화살표 키를 누르면 왼쪽으로 이동 또는 오른쪽으로 이동하도록 상태를 변경할 수 있습니다. 그런 다음 위쪽 화살표는 캐릭터를 현재 상태에서 점프 상태로 전환할 수 있으며, 마찬가지로 아래쪽 키는 캐릭터를 웅크린 상태로 전환합니다. 이것은 불완전한 예이지만 상태 다이어그램을 사용하는 방법을 이해해야 합니다. 또한 위쪽 화살표 키를 누르면 캐릭터가 위로 점프한 다음 다시 내려옵니다. 버튼을 떼지 않으면 캐릭터가 다시 점프합니다. 키를 놓으면 캐릭터가 원래 있던 상태에서 서 있는 상태로 돌아갑니다.
보다 공식적인 영역의 또 다른 예는 ATM을 고려하는 것입니다. ATM의 단순화된 상태 시스템에는 다음 상태가 포함될 수 있습니다.
기다리다
카드를 수락
PIN 항목을 수락합니다. PIN을 확인합니다.
239
베셀-바덴호르스트 2017
여 바덴호르스트, 실용적인 파이썬 디자인 패턴시간ttps://doi.org/10.1007/978-1-4842-2680-3_15
15장 상태 패턴
PIN 거부
트랜잭션 선택 가져오기
트랜잭션을 완료하는 각 트랜잭션의 각 부분에 대한 상태 세트
반환 카드
인쇄 전표
전표 발행
특정 시스템 및 사용자 작업으로 인해 ATM이 한 상태에서 다음 상태로 이동합니다. 시스템에 카드를 삽입하면 시스템이 wait 상태에서 accept_card 상태로 전환됩니다.
시스템에 대한 완전한 상태 다이어그램이 있으면 시스템이 각 단계에서 수행해야 하는 작업에 대한 상당히 완전한 그림을 갖게 됩니다. 또한 시스템이 한 단계에서 다음 단계로 어떻게 이동해야 하는지 명확하게 알 수 있습니다. 남은 것은 상태 머신 다이어그램을 코드로 변환하는 것입니다.
다이어그램을 실행 가능한 코드로 변환하는 순진한 방법은 상태 시스템을 나타내는 개체를 만드는 것입니다. 개체에는 입력에 응답하는 방법을 결정하는 상태 속성이 있습니다. 문자의 첫 번째 예를 코딩한다면 다음과 같을 것입니다.
Windows 시스템에는 curses에 문제가 있지만 Curse 라이브러리 curses와 잘 작동하는 Linux와 유사한 터미널을 제공하는 Cygwin을 설치하여 해결할 수 있습니다.
Cygwin을 다운로드하려면 https://www.cygwin.com/ 메인 웹사이트로 이동하여 컴퓨터와 일치하는 최신 DLL 버전을 다운로드하십시오. 당신이 가지고 있음을 발견했을 때
Python을 다시 설정하려면 이 책의 시작 부분에 있는 설치 가이드의 Linux 섹션에 있는 단계를 따르십시오.
가져오기 시간 가져오기 저주
메인() 정의:
승리 = curse.initscr() curse.noecho()
win.addstr(0, 0, “작업을 시작하려면 wasd 키를 누르십시오.”) win.addstr(1, 0, “종료하려면 x를 누르십시오.”)
240
15장 상태 패턴
win.addstr(2, 0, “> “) win.move(2, 2)
사실인 동안:
채널 = win.getch()
ch가 None이 아닌 경우:
win.move(2, 0) win.deleteln()
win.addstr(2, 0, “>”)
채널 == 120인 경우:
부서지다
엘프치 == 97: #ㅏ
print(“왼쪽 달리기”) elif ch == 100: #디
print(“시계 방향”) elif ch == 119: #w
print(“점프”)
엘프치 == 115: #ㅏ
print(“웅크린 자세”)
그렇지 않으면: print(“서있기”)
수면 시간(0.05)
__name__ == “__main__”인 경우:
주로()
연습으로 Chapter의 pygame 모듈로 이 코드를 확장할 수 있습니다. 4 캐릭터가 현재 수행 중인 작업을 인쇄하는 대신 화면에서 달리고 점프할 수 있는 캐릭터를 만들 수 있습니다. 화면에 단순한 블록을 그리는 대신 파일(스프라이트 시트)에서 스프라이트 이미지를 로드하는 방법을 보려면 파이게임 문서를 살펴보는 것이 도움이 될 수 있습니다.
이 시점에서 당신은 if 문이 무엇을 할지 결정하기 위해 우리가 사용하고 있다는 것을 알고 있습니다.
입력이 문제다. 코드 감각에 좋지 않은 냄새가 납니다. 상태 다이어그램은 객체 지향 시스템의 유용한 표현이고 결과 상태 머신이 널리 사용되기 때문에 이러한 코드 악취를 제거할 수 있는 디자인 패턴이 있음을 안심하십시오. 추구하는 디자인 패턴은 상태 패턴이다.
241
15장 상태 패턴
추상적인 수준에서 모든 객체 지향 시스템은 내부의 액터를 처리합니다.
각각의 행동이 다른 행위자와 시스템 전체에 어떤 영향을 미치는지. 이것이 상태 머신이 개체의 상태와 개체가 응답하도록 만드는 요소를 모델링하는 데 매우 유용한 이유입니다.
객체 지향 시스템에서 상태 패턴은 객체의 내부 상태에 따라 동작의 변형을 캡슐화하는 데 사용됩니다. 이 캡슐화는 이전 예제에서 본 모놀리식 조건문을 해결합니다.
훌륭하게 들리겠지만 이것이 어떻게 달성될까요?
필요한 것은 상태 자체에 대한 일종의 표현입니다.때로는 모든 상태가 코드에 대한 공통 기능을 공유하기를 원할 수도 있습니다.
상태 기본 클래스. 그런 다음 상태 시스템의 각 개별 상태에 대한 구체적인 상태 클래스를 만들어야 합니다. 그들 각각은 입력을 처리하고 상태 전환을 유발하는 일종의 핸들러 함수와 해당 상태에 필요한 작업을 완료하는 함수를 가질 수 있습니다.
다음 코드 스니펫에서는 구체적인 상태 클래스가 상속되는 빈 기본 클래스를 정의합니다. 이전 장에서와 같이 Python의 덕 타이핑 시스템을 사용하면 이 가장 간단한 구현에서 State 클래스를 생략할 수 있습니다. 첨부한다
명확성을 위해 스니펫의 기본 클래스 State. 구체적인 상태에는 이 구현이 어떤 작업도 수행하지 않으므로 이러한 메서드가 비어 있기 때문에 작업 메서드가 부족합니다.
클래스 상태(객체):
일어나다
Class ConcreteCondition1(조건):
def __init__(self, 상태 머신):
self.state_machine = 상태 머신
def switch_state(자신):
self.state_machine.state = 자기.state_machine.state2
Class ConcreteCondition2(조건):
def __init__(self, 상태 머신):
self.state_machine = 상태 머신
def switch_state(자신):
self.state_machine.state = 자기.state_machine.state1
StateMachine(객체) 클래스:
def __init__(self):
self.state1 = 구체적 state1(self) self.state2 = 구체적 state2(self) self.state = self.state1
def 스위치(자체):
self.state.switch_state()
데프 __str__(자신):
return str(self.state)
메인() 정의:
state_machine = StateMachine() 인쇄(state_machine)
state_machine.switch() 인쇄(state_machine)
__name__ == “__main__”인 경우:
주로()
결과에서 ConcreteState1에서 ConcreteState2로의 변경이 발생한 위치를 확인할 수 있습니다. StateMachine 클래스는 실행 컨텍스트를 나타내며 외부 세계에 대한 단일 인터페이스를 제공합니다.
<__main__.ConcreteState1-Objekt bei 0x7f184f7a7198> <__main__.ConcreteState2-Objekt bei 0x7f184f7a71d0>
더 나은 프로그래머가 되면 특정 코드 구성을 테스트하는 방법에 대해 생각하는 데 점점 더 많은 시간을 할애하게 됩니다. 상태 머신도 다르지 않습니다. 그렇다면 상태 머신에서 무엇을 테스트할 수 있습니까?
- 상태 머신이 올바르게 초기화되었는지
- 각 구체적인 상태 클래스에 대한 작업 메서드는 다음과 같이 수행해야 하는 작업을 수행합니다. B. 올바른 값을 반환하려면
243
15장 상태 패턴
- 입력이 주어지면 기계는 올바른 다음 상태로 전환합니다.
- Python은 이름이 놀랍지 않은 매우 견고한 단위 테스트 프레임워크와 함께 제공됩니다. 장치 테스트.
일반 상태 머신을 테스트하기 위해 다음 코드를 사용할 수 있습니다. import unittest
클래스 GenericStatePatternTest(unittest.TestCase):
데프 설정(자체):
self.state_machine = 스테이트머신()
def TearDown(self):
일어나다
def test_state_machine_initializes_correctly(self):
self.assertIsInstance(self.state_machine.state, 구체적인 state1)
def test_switch_from_state_1_to_state_2(self):
self.state_machine.switch()
self.assertIsInstance(self.state_machine.state, ConcreteState2)
def test_switch_from_state2_to_state1(self):
self.state_machine.switch() self.state_machine.switch()
self.assertIsInstance(self.state_machine.state, 구체적인 state1)
__name__ == ‘__main__’인 경우:
단위 테스트.메인()
assert 옵션을 가지고 놀면서 생각할 수 있는 다른 흥미로운 테스트가 무엇인지 확인하십시오.
이제 이 장의 시작 부분에서 논의한 것처럼 플레이어 캐릭터가 달리거나 걷는 문제로 돌아가겠습니다. 이전과 동일한 기능을 구현하지만 이번에는 상태 패턴을 사용합니다.
244
15장 상태 패턴
저주 가져오기
수입 시간
클래스 상태(객체):
def __init__(self, 상태 머신):
self.state_machine = 상태 머신
데프 스위치(self, in_key):
self.state_machine.mapping의 in_key인 경우:
self.state_machine.state = self.state_machine.mapping(in_key) 그렇지 않으면:
self.state_machine.state = 자기.state_machine. 매핑(“기본값”)
클래스 스탠딩(조건):
데프 __str__(자신):
“서”를 반환
클래스 RunningLeft(상태):
데프 __str__(자신):
“왼쪽으로 걷기”를 반환
클래스 RunningRight(상태):
데프 __str__(자신):
“CW”를 반환
점핑 등급(조건):
데프 __str__(자신):
“점프”를 반환
클래스 스쿼트(컨디션):
데프 __str__(자신):
반환 “웅크리고”
StateMachine(객체) 클래스:
def __init__(self):
self.standing = 서 있기(self) self.running_left = RunningLeft(self) self.running_right = RunningRight(self)
245
15장 상태 패턴
self.jumping = 점프(자신) self.crouching = 웅크리기(자신)
자기 매핑 = {
“a”: self.running_left, “d”: self.running_right, “s”: self.웅크리기, “w”: self.jumping, “default”: self.standing,
}
자태 = 자태
데프 액션(self, in_key):
self.state.switch(in_key)
데프 __str__(자신):
return str(self.state)
메인() 정의:
player1 = StateMachine() win = curses.initscr() curses.noecho()
win.addstr(0, 0, “작업을 시작하려면 wasd 키를 누르십시오.”) win.addstr(1, 0, “종료하려면 x를 누르십시오.”)
win.addstr(2, 0, “>”)
승리.이동(2, 2)
사실인 동안:
채널 = win.getch()
ch가 None이 아닌 경우:
win.move(2, 0) win.deleteln() win.addstr(2, 0, “> “) if ch == 120:
부서지다
246
15장 상태 패턴
player1.action(chr(ch))
인쇄(player1.state) time.sleep(0.05)
__name__ == “__main__”인 경우:
주로()
변경된 코드에 대해 어떻게 생각하십니까? 당신은 그것에 대해 무엇을 좋아합니까? 너는 무엇을 배웠니? 개선할 수 있는 점은 무엇이라고 생각하십니까?
온라인에서 코드를 살펴보고 스스로에게 이러한 질문을 해보시기 바랍니다. 다른 사람의 코드를 읽는 것이 온라인에서 찾을 수 있는 모든 자습서보다 더 많은 것을 가르쳐준다는 것을 종종 알게 될 것입니다. 또한 온라인에서 찾은 코드를 단순히 프로젝트에 복사하고 최선을 다하기보다 비판적으로 생각하기 시작할 때 학습에서 또 다른 큰 진전을 이루는 곳이기도 합니다.
이 장에서 우리는 파이썬의 실제 코드를 여러 유형의 문제, 즉 상태 머신을 해결하기 위한 추상 도구에 얼마나 밀접하게 연결할 수 있는지 발견했습니다.
모든 상태 머신은 특정 입력을 기반으로 머신을 한 상태에서 다른 상태로 가져오는 상태 및 전환으로 구성됩니다. 일반적으로 상태 시스템은 다른 상태로 전환하기 전에 한 상태에 있는 동안 몇 가지 작업을 수행합니다.
또한 Python에서 고유한 상태 시스템을 만드는 데 사용할 수 있는 실제 코드도 살펴보았습니다.
다음은 자신만의 상태 시스템 기반 솔루션을 만들기 위한 빠르고 쉬운 아이디어입니다.
- 예를 들어 컴퓨터가 있을 수 있는 상태를 식별합니다. 나. 뛰거나 걷거나 신호등이 빨강, 노랑, 초록인 경우
- 각 상태에 대해 기대하는 다양한 입력을 식별합니다.
- 입력에 따라 현재 상태에서 다음 상태로의 전환을 그립니다. 전환 라인의 입력에 주목하십시오.
- 각 상태에서 머신이 수행하는 작업을 정의합니다.
- 공유 작업을 기본 상태 클래스로 추상화합니다.
- 식별한 각 상태에 대해 구체적인 클래스를 구현합니다.
247
15장 상태 패턴
- 각 상태에 대해 예상되는 입력을 처리하기 위해 일련의 전환 방법을 구현합니다.
- 각 상태에서 머신이 수행해야 하는 작업을 구현합니다. 이러한 작업은 구체적인 State 클래스와 기본 State 클래스 모두에 존재한다는 점을 기억하십시오.
거기에는 문제를 해결하는 추상 다이어그램과 일대일 관계를 갖는 완전히 구현된 상태 머신이 있습니다.
다음을 사용하여 플레이어 캐릭터의 단순 상태 머신을 확장합니다.
비주얼 버전을 만들기 위한 파이게임. 플레이어를 위한 스프라이트를 로드한 다음 점프 동작을 위한 몇 가지 기본 물리학을 모델링합니다.
사용 가능한 다양한 유형의 Assert 문 탐색
Python unittest 라이브러리의 일부입니다.