上下文:我正在尝试使用TypeState库开发用于在typescript中创建可扩展状态机的模式。 TypeState为Typescript提供了一个类型安全的状态机,虽然不是问题的核心,但我有这个帮助说明我的目标。
问题:我遇到了创建可扩展模式的问题,以便在Typescript中扩展enum
并在interface
和class
声明中实现它们。
目标:下面的伪代码说明了我希望我的模式看起来像什么。
1)定义基础enum States
2)使用其他状态扩展enum States
,结果为enum ExtendedStates
2)使用ParentInterface
和键入状态机
States
3)通过ParentInterface
扩展ChildInterface
并使用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
中的大量代码重复。它也失去了接口,这不是一个要求,但提供了一个很好的安全网。
我很想听听你们所说的话。我觉得这是一个强大的模式,我为了实现它而缺少一些简单的东西。
答案 0 :(得分:2)
无法编译的一个原因是ul {
display: block;
font-size: 30px;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
font-weight: bold;
color: red;
}
不是Child
的正确子类型。 Liskov substitution principle表示您应该能够将Parent
对象用作Child
对象。如果我问一个Parent
对象状态机所处的状态,它告诉我Parent
,那么我有一个损坏的ExtendedState
,对吧?因此,Parent
是一个损坏的Child
,这是不好的,也是TypeScript警告你的。
可能最好忘记拥有超类/子类关系并且只有一个泛型类:
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
库,所以我不知道它是否关心数值或字符串。)现在你可以像这样创建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);
希望有所帮助;祝你好运!