微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我正在尝试一起实现一个通用的规范模式和一个通用的访问者模式。这是我的基本接口。

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>;
  or(other: Specification<T,TVisitor>;
  orNot(other: 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,T>> implements Specification<T,TVisitor> {
  abstract isSatisfiedBy(candidate: T): boolean;
  abstract accept(visitor: TVisitor): void;

  and(other: Specification<T,TVisitor> {
    return new AndSpecification<T,TVisitor>(this,other);
  }
  andNot(other: Specification<T,TVisitor> {
    return new AndNotSpecification<T,other);
  }
  or(other: Specification<T,TVisitor> {
    return new OrSpecification<T,other);
  }
  orNot(other: Specification<T,TVisitor> {
    return new OrNotSpecification<T,other);
  }
  not(): Specification<T,TVisitor> {
    return new NotSpecification<T,TVisitor>(this);
  }
}

export class AndSpecification<T,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,T>> extends CompositeSpecification<T,TVisitor> {
  constructor(readonly left: 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>) {
    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>) {
    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
> {
  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,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)

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

解决方法

哇,里面有相当多的代码。让我们将其缩减为显示相同问题的 minimal reproducible example

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

interface BadBar extends Foo {  // error!
  ovld(x: boolean): string;
}

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


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

interface XXX {
  prop: string;
}
interface YYY extends XXX { // error!
  prop: boolean; 
}

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


那么,如果我们不能仅仅通过在 ovld() 中重新声明 BadBar 来添加重载,我们如何做到这一点?一种方法是使用 indexed access type 从父接口获取现有的调用签名,然后使用新的调用签名intersect

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"]
  );
}

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

Playground link to code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?