创建可扩展枚举用于可扩展接口
上下文:我想开发一个模式,使用TypeState库在打字稿中创建可扩展状态机。 TypeState为Typescript提供了一个类型安全的状态机,虽然不是问题的核心,但它有助于说明我的目标。创建可扩展枚举用于可扩展接口
问题:我遇到了在打字稿扩展enum
和interface
和class
声明实现它们创造一个可扩展的模式问题。
目标:下面的psuedocode说明了我想让我的模式看起来像什么。
1)定义基enum States
2)扩展enum States
与附加状态导致enum ExtendedStates
2)使用States
定义ParentInterface
和输入状态机
3)经由ChildInterface
扩展ParentInterface
并重写States
与ExtendedStates
4)实施ParentInterface
在class Parent
5)扩展class Parent
在class 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
很多重复的代码不编译和结果。它也失去了界面,这不是要求,但提供了一个很好的安全网。
我很想听听你们都说了些什么。我觉得这是一种强大的模式,我错过了一些简单的事情来实现它。
它不编译的一个原因是因为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
库,所以我不知道它是否关心,如果值现在你可以创建你的States
和ExtendedStates
像这样:
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);
希望帮助;祝你好运!
伟大的答案,我认为这将工作得很好。 – gjolund