尝试实现通用规范和访问者模式时,类型不满足约束并错误地扩展了接口

sno*_*dev 4 generics types visitor-pattern specification-pattern 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;
}
Run Code Online (Sandbox Code Playgroud)

为方便起见,我为基本布尔运算符实现了一些基类和一个抽象类。

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

以上所有工作和编译没有错误。但是,当我尝试创建一个扩展基本 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

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;
}
Run Code Online (Sandbox Code Playgroud)

我不太明白它为什么抱怨。我需要改变什么才能让它按照我想要的方式工作?

jca*_*alz 5

哇,里面有相当多的代码。让我们将其缩减为显示相同问题的最小可重现示例

interface Foo {
  ovld(x: string): number;
  ovld(x: number): boolean;
}

interface BadBar extends Foo {  // error!
  ovld(x: boolean): string;
}
Run Code Online (Sandbox Code Playgroud)

这里有一个Foo接口,其中包含一个名为的重载方法ovld。这个方法有两个调用签名;一个需要 a string,另一个需要 a number。我们尝试制作BadBar扩展它的接口。我们的目的是添加第三个重载,ovld它需要一个boolean. 但它不起作用!为什么?


答案是在扩展接口时不能简单地添加重载。接口扩展不是 接口合并。通过ovld在扩展接口中重新声明,您是在告诉编译器用in版本完全覆盖ovldin的类型。并且因为单个调用签名 in不能分配给 in 中的调用签名,这是一个错误,就像这是一个错误:FooBadBarBadBarFoo

interface XXX {
  prop: string;
}
interface YYY extends XXX { // error!
  prop: boolean; 
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,您都通过采用现有属性或方法并进行无效更改来错误地扩展接口。唯一可接受的更改是缩小更改,例如将 a 更改string"a" | "b"


所以,如果我们不能再仅仅宣布加过载ovld()BadBar,怎么我们做的呢?一种方法是使用索引访问类型从父接口获取现有调用签名,然后将其与新调用签名相交

interface GoodBar extends Foo {
  ovld: ((x: boolean) => string) & Foo["ovld"]
} 
/* (property) GoodBar.ovld: ((x: boolean) => string) & {
  (x: string): number;
  (x: number): boolean;
} */
Run Code Online (Sandbox Code Playgroud)

交集X & Y将被视为 的有效缩小X,因此编译器错误消失了。此外,TypeScript 认为函数的交集等同于重载。所以上面给你新的(x: boolean) => string调用签名作为第一个重载,然后是现有的两个重载签名Foo

declare const goodBar: GoodBar;
goodBar.ovld(false).toUpperCase();
goodBar.ovld("hello").toFixed();
goodBar.ovld(123) === true;
Run Code Online (Sandbox Code Playgroud)

万岁!


这意味着您NumberComparatorVisitor可能需要更改为以下内容:

export interface NumberComparatorVisitor extends
  SpecificationVisitor<NumberComparatorVisitor, number> {
  visit: (
    ((specification: GreaterThan) => void) &
    SpecificationVisitor<NumberComparatorVisitor, number>["visit"]
  );
}
Run Code Online (Sandbox Code Playgroud)

如果我进行了更改,那么错误就会消失,因为现在NumberComparatorVisitor确实是其父界面的有效扩展。

Playground 链接到代码

  • 很好的解释,现在我明白了。这个修复有点令人讨厌,但至少是有用的。我可能会放弃使用方法重载,而只是使用单独的不同方法。 (2认同)