2017-09-03 21 views
0

コンテキスト:TypeStateライブラリを使用して、typescriptで拡張可能ステートマシンを作成するパターンを開発しようとしています。 TypeStateはTypescriptのための型保証された状態マシンを提供しますが、問題の中心ではありませんが、私はそれが私の目標を説明するのを助けています。拡張可能なインターフェイスで使用するための拡張可能な列挙型を作成する

問題:私は活字体でenumを拡張し、interfaceclass宣言でそれらを実装するためのスケーラブルなパターンを作成する問題に実行しています。

目標:以下の擬似コードは、自分のパターンがどのように見えるかを示しています。

1)基地enum States

2)Statesを用いParentInterfaceを定義enum ExtendedStates

2)で得られた追加の状態とenum Statesを拡張し、入力したステートマシン

3)ChildInterface介しParentInterfaceを拡張し、Statesをオーバーライド定義with ExtendedStates

4)のいずれかのクラスからbroadcastState()を呼び出し、現在の状態を取得することができる)ChildInterface

6を実装class Childclass Parentを拡張)class Parent

5でParentInterfaceを実装します。

私はこのパターンを他の言語でも効果的に使用しています。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

ChildParentの適切なサブタイプではないため、コンパイルできない理由の1つは、 Liskov substitution principleChildオブジェクトをParentオブジェクトとして使用できるはずだと言います。ステートマシンの状態がParentのオブジェクトを尋ねると、ExtendedStateと表示されたら、壊れたParentが表示されます。だからChildは壊れているParentです。これは悪いです。

は、おそらくそれは、スーパークラス/サブクラス関係を有する忘れた方が良いですし、ちょうどジェネリッククラスを持っている:

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とは違って、値が数字の場合は、実際に数字を並べ替えることができますが、実装するのは面倒です。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

関連する問題