2017-09-03 179 views
0

上下文:我想開發一個模式,使用TypeState庫在打字稿中創建可擴展狀態機。 TypeState爲Typescript提供了一個類型安全的狀態機,雖然不是問題的核心,但它有助於說明我的目標。創建可擴展枚舉用於可擴展接口

問題:我遇到了在打字稿擴展enuminterfaceclass聲明實現它們創造一個可擴展的模式問題。

目標:下面的psuedocode說明了我想讓我的模式看起來像什麼。

1)定義基enum States

2)擴展enum States與附加狀態導致enum ExtendedStates

2)使用States定義ParentInterface和輸入狀態機

3)經由ChildInterface擴展ParentInterface並重寫StatesExtendedStates

4)實施ParentInterfaceclass Parent

5)擴展class Parentclass Child實施ChildInterface

6)能夠調用broadcastState()從任一類,並獲得當前的狀態。

我已經在其他語言中使用了這種模式,對於理解Typescript的侷限性和任何可以實現相同目標的替代模式我都會很感激。

import {TypeState} from "typestate"; 

enum States { 
    InitialState 
} 

// extends is not available on enum, looking for alternative 
enum ExtendedStates extends States { 
    AdditionalState 
} 

///////////////////////////////////////// 
// this works fine 
interface ParentInterface { 
    fsm: TypeState.FiniteStateMachine<States>; 
    states: typeof States; 
    message: string; 
} 

// incorrectly extends ParentInterface, types of fsm/states are incompatible 
interface ChildInterface extends ParentInterface { 
    fsm: TypeState.FiniteStateMachine<ExtendedStates>; 
    states: typeof ExtendedStates; 
} 

///////////////////////////////////////// 

class Parent implements ParentInterface { 
    public fsm: TypeState.FiniteStateMachine<States>; 
    public states: typeof States; 
    public message: string = "The current state is: "; 

    constructor(state: States | undefined) { 
    state = state ? state : this.states.InitialState; 
    this.fsm = new TypeState.FiniteStateMachine(state); 
    this.broadcastCurrentState(); 
    } 

    public broadcastCurrentState(): void { 
    console.log(this.message + this.fsm.currentState); 
    } 
} 

class Child extends Parent implements ChildInterface { 
    public fsm: TypeState.FiniteStateMachine<ExtendedStates>; 
    public states: typeof ExtendedStates; 

    constructor(state: ExtendedStates | undefined) { 
    state = state ? state : this.states.InitialState; 
    this.fsm = new TypeState.FiniteStateMachine(ExtendedStates); 
    this.broadcastCurrentState(); 
    } 
} 

最近我已經得到

import {TypeState} from "typestate"; 

enum States { 
    InitialState 
} 

enum ExtendedStates { 
    InitialState, 
    ExtendedState 
} 

class Parent { 
    public fsm: TypeState.FiniteStateMachine<States>; 
    public states: typeof States; 
    public message: string = "The current state is: "; 

    // T is declared but never used 
    constructor(state: <T> | undefined) { 
    state = state ? state : this.states.InitialState; 
    // cannot find name T 
    this.fsm = new TypeState.FiniteStateMachine<T>(state); 
    this.broadcastCurrentState(); 
    } 

    public broadcastCurrentState(): void { 
    console.log(this.message + this.fsm.currentState); 
    } 
} 

// types of fsm are incompatible 
class Child extends Parent { 
    public fsm: TypeState.FiniteStateMachine<ExtendedStates>; 
    public states: typeof ExtendedStates; 

    constructor(state: ExtendedStates | undefined) { 
    // Param not assignable to type <T> 
    super(state); 
    } 
} 

這種嘗試得到接近理想的結果,但在enum很多重複的代碼不編譯和結果。它也失去了界面,這不是要求,但提供了一個很好的安全網。

我很想聽聽你們都說了些什麼。我覺得這是一種強大的模式,我錯過了一些簡單的事情來實現它。

回答

2

它不編譯的一個原因是因爲Child不是Parent的適當子類型。 Liskov substitution principle表示您應該能夠使用Child對象作爲Parent對象。如果我詢問一個Parent對象狀態機處於哪個狀態,並且它告訴我ExtendedState,那麼我有一個壞的Parent,對吧?所以Child是一個壞的Parent,這是不好的,這是TypeScript警告你的。

也許是更好的具有超/子關係忘記,只是有一個通用類:

class Generic<T extends States> { 
    public fsm: TypeState.FiniteStateMachine<T>; 
    public states: T; 
    public message: string = "The current state is: "; 

    // T[keyof T] means the values of T, in this case InitialState, etc  
    constructor(state: T[keyof T] | undefined) { 
    state = state ? state : this.states.InitialState; 
    // cannot find name T 
    this.fsm = new TypeState.FiniteStateMachine<T>(state); 
    this.broadcastCurrentState(); 
    } 

    public broadcastCurrentState(): void { 
    console.log(this.message + this.fsm.currentState); 
    } 
} 

現在想,如果工作States是正確的類的對象,但你可以注意到,enum並不是真的具有足夠的功能以便以這種方式使用:你不能得到任何東西來擴展它們。因此,而不是使用enum,爲什麼不使用它模擬它的對象:

// make our own enum 
type Enum<T extends string> = {[K in T]: K}; 

// create an enum from given values 
function makeEnum<T extends string>(...vals: T[]): Enum<T> { 
    const ret = {} as Enum<T>; 
    vals.forEach(k => ret[k] = k) 
    return ret; 
} 

// take an existing enum and extend it with more values 
function extendEnum<T extends string, U extends string>(
    firstEnum: Enum<T>, ...vals: U[]): Enum<T | U> { 
    return Object.assign(makeEnum(...vals), firstEnum) as any; 
} 

在這種情況下,Enum<>與指定的字符串鍵,其值是一樣的鍵的對象(這有點不同於常規的enum s其數值是數字如果你真的想要的數字可能可以安排,但它會更加惱人的實施我從來沒有使用TypeState庫,所以我不知道它是否關心,如果值現在你可以創建你的StatesExtendedStates像這樣:

const States = makeEnum('InitialState'); 
type States = typeof States; 
// States is { InitialState: 'InitialState' }; 

const ExtendedStates = extendEnum(States, 'ExtendedState'); 
type ExtendedStates = typeof ExtendedStates; 
// ExtendedStates is { InitialState: 'InitialState', ExtendedState: 'ExtendedState' }; 

和創建對象是這樣的:

const parentThing = new Generic<States>(States.InitialState); 
const childThing = new Generic<ExtendedStates>(ExtendedStates.InitialState); 

希望幫助;祝你好運!

+0

偉大的答案,我認爲這將工作得很好。 – gjolund