타자 원고에서 예를 들어 설명한 상태 모드

Source Code on GitHub | Original Article on my Blog |
본고에서 State 모델을 소개하고 토스터를 예로 들어 TypeScript에서 이를 실현하는 방법을 보여 드리겠습니다.이 과정에서 나는 유한상태기의 개념을 토론하고 상태 모델이 어떻게 우리가 그것들을 실현하는 데 도움을 줄 수 있는지 설명할 것이다.

상태 모드: 상태기 행동 모드 실현


상태 모델은 행위 설계 모델에 속하기 때문에 통신과 위탁 문제를 해결하는 데 쓰인다.같은 그룹의 다른 모델은 관찰자, 방문자, 명령을 포함한다.
기본적으로 상태 모드는 봉인 대상의 내부 상태를 통해 행동을 변경할 수 있도록 한다.하나의 대상을 처리할 때 이런 모델은 매우 유용하다. 이 대상은 여러 가지 뚜렷한 상태를 나타낼 수 있고 이런 상태 사이를 전환해야 한다.봉인은 우리가 한 번에 특정한 상태만 주목하고 그 행위가 하나의 단독 클래스와 일치하도록 유지하며, 얽힌 다상태 논리로 주위의 (상하문) 클래스를 교란하지 않도록 한다.
따라서 상태 모델은 이른바 유한상태기에 적합하다.

A Finite State Machine (FSM) is any kind of device that can take on exactly one of a finite set of states (= specific behaviors) at a time and allows transitioning between such states due to external input.


유한 상태기의 예로는 교통 신호등(빨간색 상태, 주황색 상태, 녹색 상태), ATM(예를 들어 카드 삽입 상태, PIN 입력 상태)과 토스터(예를 들어 빈 상태, 토스터 상태)가 있다.후자는 좋은 예로 어떻게 상태 모델을 실현하는지 설명할 것이다.

토스터:유한상태기


위의 FSM에 대한 정의를 감안하여 우리는 상태도를 이용하여 토스터의 다양한 상태와 전환을 포착할 수 있다.

처음에는 제빵기Idle 상태로 누군가Insert Bread가 열리기를 기다리다가 Bread Inserted 상태로 전환된다.장치는 조종대가 당겨져 Pull Lever변환되어 Toasting상태로 들어갈 때까지 이 상태를 유지할 것이다.일정 시간이 지나면 빵이 튀어나오고Eject Bread 변환되며, 토스터는 그BreadEjected 상태로 전환된다.마지막으로 빵이 제거(Remove Bread변환)되고 설비는 다시 초기Idle상태로 돌아간다.
이것들은 단지 매우 기본적인 상태와 전환일 뿐이라는 것을 주의하세요.모든 상태(예를 들어 가열, 냉각)는 진일보한 행위 논리가 없고 구체적인 토스터 모델에 따라 더 많은 상태와 전환(예를 들어 빵이 자동으로 튀어나오는 타이머)이 존재한다.
또 주의해야 할 것은 주어진 전환 외에 상태 사이를 전환할 수 있는 다른 방법이 없다는 것이다.특히 Idle 상태를 거치지 않으면 Toasting 상태에서 Bread Inserted 상태로 바로 전환할 수 없다.

TypeScript로 토스터 구현


TypeScript에서 우리의 토스터 예시를 실현하기 시작합시다.디바이스 작업을 수집하는 인터페이스의 정의부터 시작하겠습니다.
interface ToasterOperations {
    insertBread(): void
    pullLever(): void
    ejectBread(): void
    removeBread(): void
}
너는 토스터의 조작이 그것의 전환과 완전히 일치하는 것을 볼 수 있다.현재 우리는 ToasterOperations류에서 실현할 것이다Toaster.
class Toaster implements ToasterOperations {
    public insertBread(): void {
        // ...
    }
    public pullLever(): void {
        // ...
    }
    public ejectBread(): void {
        // ...
    }
    public removeBread(): void {
        // ...
    }
}
지금까지 이곳에는 별다른 일이 없었다.다음에 우리는 전환 방법을 호출한 후에 조작 논리를 처리해야 한다.만약 우리가 직접 Toaster류에서 이렇게 한다면 우리는 불투명하고 일관성이 없는 파스타 코드를 많이 얻을 수 있을 것이다.어쨌든 모든 방법에서 촉발하는 정확한 행위는 Toaster의 현재 상태에 달려 있다.
우리는 정말 교실에서 많은 if... then... else 문장과 상응하는 상태 논리를 함부로 버리고 싶지 않다.반대로 현재 토스터 상태 자체로 전환할 때 발생하는 조작 논리를 호출할 것입니다.이를 위해, 우리는 서로 다른 상태 클래스 (기본적으로 상태 모드를 사용하여 우리의 행위를 봉함) 를 정의하고, 그것들이 우리의 ToasterOperations 인터페이스를 실현하도록 해야 한다.
우리는 하나의 기류ToasterState부터 시작하고 다른 상태류는 그 중에서 계승할 것이다.각기 다른 상태는 자신의State류에서 이루어진다.기본적으로 이러한 방법에서 호출되는 모든 방법을 확보하면 투매를 초래할 수 있다Error.어쨌든 우리는 Toaster 정의가 명확한 전환 외에 상태를 바꾸는 것을 원하지 않는다.
그러나 호출된 방법이curren 상태에서 전환된 것이 확실하면 오류가 발생하지 않습니다.반대로 과도해야 한다.이를 위해, 우리는 구체적인 하위 클래스에서 상응하는 변환 방법을 다시 써야 한다. 호출된 방법은 토스터가 변환한 새로운 State 의 실례를 되돌려준다.
abstract class ToasterState implements ToasterOperations
{
    public insertBread(): ToasterState {
        throw new Error("Invalid operation")
    }
    public pullLever(): ToasterState {
        throw new Error("Invalid operation")
    }
    public ejectBread(): ToasterState {
        throw new Error("Invalid operation")
    }
    public removeBread(): ToasterState {
        throw new Error("Invalid operation")
    }
}
초기Idle상태에 대해 우리는 IdleState류를 실현하고 다시 쓰기insertBread()방법을 실현할 것이다.빵이 토스터에 삽입되었을 때, 우리는 그것을 강제로 Bread Inserted상태로 전환시킬 것이다. 이것은 BreadInsertedState류에서 실현된 것이다
class IdleState extends ToasterState
{
    public insertBread(): ToasterState {
        return new BreadInsertedState()
    }
}
마찬가지로, 우리는 각자의 주 유형에서 우리의 다른 주를 실시할 것이다.
class BreadInsertedState extends ToasterState
{
    public pullLever(): ToasterState {
        return new ToastingState()
    }
}

class ToastingState extends ToasterState
{
    public ejectBread(): ToasterState {
        return new BreadEjectedState()
    }
}

class BreadEjectedState extends ToasterState
{
    public removeBread(): ToasterState {
        return new IdleState()
    }
}
지금 우리가 해야 할 일은 대상의 내부 상태를 바꾸는 것을 허락하는 것이다.이것은 일부 변수를 통해 토스터의 현재 상태를 인용하여 완성할 것이다.그리고 Toaster 대상의 모든 전환 방법에 대한 호출은 현재Toaster의 실례에 의뢰됩니다.
따라서 Toaster 클래스는 다음과 같이 수정됩니다.
class Toaster implements ToasterOperations
{
    // this will hold our current state and is
    // initialized with the initial state "Idle"
    private _state: ToasterState = new IdleState()

    constructor() {
        this.logCurrentState()
    }

    public insertBread(): void {
        this._state = this._state.insertBread()
        this.logCurrentState()
    }
    public pullLever(): void {
        this._state = this._state.pullLever()
        this.logCurrentState()
    }
    public ejectBread(): void {
        this._state = this._state.ejectBread()
        this.logCurrentState()
    }
    public removeBread(): void {
        this._state = this._state.removeBread()
        this.logCurrentState()
    }

    // used for testing purposes to print out the current state
    // the toaster object is in
    private logCurrentState(): void {
        console.log(this._state)
    }
}
ToasterState 논리에서 볼 수 있듯이, 그 중 하나의 변환 방법에 대한 호출은 로컬 Toaster 변수의 인용을 새로운 실례로 설정합니다.물론 현재 호출 변환이 허용되지 않는 한, 이 경우, 우리의 _state 기류에 따라 Error 던집니다.
매번 전환할 때마다 새로운 상태는 콘솔에 기록되어 테스트를 진행한다.
우리의 실현을 시험해 봅시다.
var toaster = new Toaster()
// logs: IdleState {}

toaster.insertBread()
// logs: BreadInsertedState {}

toaster.pullLever()
// logs: ToastingState {}

toaster.ejectBread()
// logs: BreadEjectedState {}

toaster.removeBread()
// logs: IdleState {}
이것은 매우 효과가 있다.만약 우리가 현재 허용하는 방법을 호출한다면, 실현된 행위를 살펴보자.
var toaster = new Toaster()
// logs: IdleState {}

toaster.ejectBread()
// logs: Error: Invalid operation
이렇게우리의 토스터는 매우 잘 작동하고 있으며, 응용 상태 모델을 통해 우리는 상태 논리의 깨끗함과 집중을 성공적으로 유지하였다.
기본 전환과 상태를 확장하는 것은 매우 간단한 임무이며 오염ToasterState류를 걱정할 필요가 없다.그러나 나는 기꺼이 이 임무를 독자들에게 연습으로 남겨 두겠다.
Source on GitHub: https://github.com/fellingsoftware/FS.Articles.StatePattern

좋은 웹페이지 즐겨찾기