2017-09-03 129 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