typescript - 尝试实现通用规范和访问者模式时,类型不满足约束并错误地扩展接口
问题描述
我正在尝试一起实现通用规范模式和通用访问者模式。这是我的基本接口。
export interface Specification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> {
accept(visitor: TVisitor): void;
isSatisfiedBy(candidate: T): boolean;
and(other: Specification<T, TVisitor>): Specification<T, TVisitor>;
andNot(other: Specification<T, TVisitor>): Specification<T, TVisitor>;
or(other: Specification<T, TVisitor>): Specification<T, TVisitor>;
orNot(other: Specification<T, TVisitor>): Specification<T, TVisitor>;
not(): Specification<T, TVisitor>;
}
export interface SpecificationVisitor<TVisitor extends SpecificationVisitor<TVisitor, T>, T> {
visit(specification: AndSpecification<T, TVisitor>): void;
visit(specification: AndNotSpecification<T, TVisitor>): void;
visit(specification: OrSpecification<T, TVisitor>): void;
visit(specification: OrNotSpecification<T, TVisitor>): void;
visit(specification: NotSpecification<T, TVisitor>): void;
}
为方便起见,我为基本布尔运算符实现了一些基类和一个抽象类。
export abstract class CompositeSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> implements Specification<T, TVisitor> {
abstract isSatisfiedBy(candidate: T): boolean;
abstract accept(visitor: TVisitor): void;
and(other: Specification<T, TVisitor>): Specification<T, TVisitor> {
return new AndSpecification<T, TVisitor>(this, other);
}
andNot(other: Specification<T, TVisitor>): Specification<T, TVisitor> {
return new AndNotSpecification<T, TVisitor>(this, other);
}
or(other: Specification<T, TVisitor>): Specification<T, TVisitor> {
return new OrSpecification<T, TVisitor>(this, other);
}
orNot(other: Specification<T, TVisitor>): Specification<T, TVisitor> {
return new OrNotSpecification<T, TVisitor>(this, other);
}
not(): Specification<T, TVisitor> {
return new NotSpecification<T, TVisitor>(this);
}
}
export class AndSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> extends CompositeSpecification<
T,
TVisitor
> {
constructor(readonly left: Specification<T, TVisitor>, readonly right: Specification<T, TVisitor>) {
super();
}
accept(visitor: TVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: T): boolean {
return this.left.isSatisfiedBy(candidate) && this.right.isSatisfiedBy(candidate);
}
}
export class AndNotSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> extends CompositeSpecification<T, TVisitor> {
constructor(readonly left: Specification<T, TVisitor>, readonly right: Specification<T, TVisitor>) {
super();
}
accept(visitor: TVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: T): boolean {
return this.left.isSatisfiedBy(candidate) && !this.right.isSatisfiedBy(candidate);
}
}
export class OrSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> extends CompositeSpecification<
T,
TVisitor
> {
constructor(readonly left: Specification<T, TVisitor>, readonly right: Specification<T, TVisitor>) {
super();
}
accept(visitor: TVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: T): boolean {
return this.left.isSatisfiedBy(candidate) || this.right.isSatisfiedBy(candidate);
}
}
export class OrNotSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> extends CompositeSpecification<
T,
TVisitor
> {
constructor(readonly left: Specification<T, TVisitor>, readonly right: Specification<T, TVisitor>) {
super();
}
accept(visitor: TVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: T): boolean {
return this.left.isSatisfiedBy(candidate) || !this.right.isSatisfiedBy(candidate);
}
}
export class NotSpecification<T, TVisitor extends SpecificationVisitor<TVisitor, T>> extends CompositeSpecification<
T,
TVisitor
> {
constructor(readonly other: Specification<T, TVisitor>) {
super();
}
accept(visitor: TVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: T): boolean {
return !this.other.isSatisfiedBy(candidate);
}
}
所有上述工作和编译没有错误。但是当我尝试创建一个扩展基本 SpecificationVisitor 接口的接口并实现一个扩展抽象 CompositeSpecification 的类时遇到编译器问题。
export interface NumberComparatorVisitor extends SpecificationVisitor<NumberComparatorVisitor, number> {
visit(specification: GreaterThan): void;
}
export class GreaterThan extends CompositeSpecification<number, NumberComparatorVisitor> {
constructor(readonly value: number) {
super();
}
accept(visitor: NumberComparatorVisitor): void {
visitor.visit(this);
}
isSatisfiedBy(candidate: number): boolean {
return candidate > this.value;
}
}
我收到以下错误:
Type 'NumberComparatorVisitor' does not satisfy the constraint 'SpecificationVisitor<NumberComparatorVisitor, number>'.ts(2344)
Interface 'NumberComparatorVisitor' incorrectly extends interface 'SpecificationVisitor<NumberComparatorVisitor, number>'.
Types of property 'visit' are incompatible.
Type '(specification: GreaterThan) => void' is not assignable to type '{ (specification: AndSpecification<number, NumberComparatorVisitor>): void; (specification: AndNotSpecification<number, NumberComparatorVisitor>): void; (specification: OrSpecification<...>): void; (specification: OrNotSpecification<...>): void; (specification: NotSpecification<...>): void; }'.
Types of parameters 'specification' and 'specification' are incompatible.
Type 'AndSpecification<number, NumberComparatorVisitor>' is not assignable to type 'GreaterThan'.ts(2430)
Type 'NumberComparatorVisitor' does not satisfy the constraint 'SpecificationVisitor<NumberComparatorVisitor, number>'.
Types of property 'visit' are incompatible.
Type '(specification: GreaterThan) => void' is not assignable to type '{ (specification: AndSpecification<number, NumberComparatorVisitor>): void; (specification: AndNotSpecification<number, NumberComparatorVisitor>): void; (specification: OrSpecification<...>): void; (specification: OrNotSpecification<...>): void; (specification: NotSpecification<...>): void; }'.
Types of parameters 'specification' and 'specification' are incompatible.
Property 'value' is missing in type 'AndSpecification<number, NumberComparatorVisitor>' but required in type 'GreaterThan'.ts(2344)
我不太明白它为什么抱怨。我需要改变什么才能让它按照我想要的方式工作?
解决方案
哇,里面有很多代码。让我们将其缩减为一个显示相同问题的最小可重现示例:
interface Foo {
ovld(x: string): number;
ovld(x: number): boolean;
}
interface BadBar extends Foo { // error!
ovld(x: boolean): string;
}
在这里,您有一个Foo
带有重载方法的接口,名为ovld
. 该方法有两个调用签名;一个需要 a string
,另一个需要 a number
。我们尝试制作BadBar
扩展它的接口。我们的目的是添加第三个重载,ovld
它需要一个boolean
. 但它不起作用!为什么?
答案是在扩展接口时不能简单地添加重载。接口扩展不是 接口合并。通过在扩展接口中重新声明,您是在告诉编译器用 in的版本ovld
完全覆盖 in 的ovld
类型。并且因为 in 中的单个调用签名不能分配给 in 中的调用签名,所以这是一个错误,就像这是一个错误:Foo
BadBar
BadBar
Foo
interface XXX {
prop: string;
}
interface YYY extends XXX { // error!
prop: boolean;
}
在这两种情况下,您都通过采用现有属性或方法并进行无效更改来错误地扩展接口。唯一可接受的更改是缩小更改,例如将 a 更改string
为"a" | "b"
.
那么,如果我们不能仅仅通过重新声明ovld()
in来BadBar
添加重载,我们该怎么做呢?一种方法是使用索引访问类型从父接口获取现有调用签名,然后将其与新调用签名相交:
interface GoodBar extends Foo {
ovld: ((x: boolean) => string) & Foo["ovld"]
}
/* (property) GoodBar.ovld: ((x: boolean) => string) & {
(x: string): number;
(x: number): boolean;
} */
交集X & Y
将被视为 的有效缩小X
,因此编译器错误消失。此外,TypeScript 认为函数的交集等同于重载。因此,上面为您提供了新的(x: boolean) => string
调用签名作为第一个重载,然后是现有的两个重载签名Foo
:
declare const goodBar: GoodBar;
goodBar.ovld(false).toUpperCase();
goodBar.ovld("hello").toFixed();
goodBar.ovld(123) === true;
万岁!
这意味着您NumberComparatorVisitor
可能需要更改为以下内容:
export interface NumberComparatorVisitor extends
SpecificationVisitor<NumberComparatorVisitor, number> {
visit: (
((specification: GreaterThan) => void) &
SpecificationVisitor<NumberComparatorVisitor, number>["visit"]
);
}
如果我进行更改,那么错误就会消失,因为 nowNumberComparatorVisitor
确实是其父接口的有效扩展。