什么时候在 TypeScript 中使用 Generic Class?

When to use Generic Class in TypeScript?

文档中有一个例子,但不清楚如何在实践中使用:

class Result<T> {
 constructor(public wasSuccessful: boolean, public error: T) {
 }
 public clone(): Result<T> {
 ...
 }
}
let r1 = new Result(false, 'error: 42');

例如,您可能有摘要 class 和正在与 class 的 T 个子项一起工作的集合。

abstract class BaseModel<T extends BaseModel<{}>>{
  abstract get(): any;
  abstract create(): any;
  abstract etc(): this;
}

class ProductModel extends BaseModel<ProductModel>{
  etc(){
    return this;
  }

  get(){
    console.log('Got somehing for product model');    
  }

  create(){
    console.log('Created something');
  }
}

class Service<T extends BaseModel<{}>>{

  constructor(private model: T){

  }

  getSomething(){
    return this.model.get();
  }
}

let service = new Service(new ProductModel());

service.getSomething();

更新:正如您询问的示例

abstract class BaseItem{

}

class Item extends BaseItem{

}

class OpenableItem extends BaseItem{

}

class MultiLineItem extends BaseItem{

}

class Menu<T extends BaseItem>{
  private items: T[];  
}

可能可以使用接口 Item 并为每个项目做单独的 classes,但如果有相同的功能,你最好使用抽象 class.

这是一个相当抽象的问题,如果不了解您的编程和一般面向对象概念的经验水平,很难正确回答这个问题。

也许 TypeScript 以及任何其他具有泛型类型的语言中存在的最简单有用的泛型类型是泛型数组或列表。

您可以在不知道数组包含的确切类型的情况下对数组执行一些操作。 Array.prototype.map的简单例子例如:

function map<T, R>(array: Array<T>, callback: (t: T) => R): Array<R> {
    const ret: Array<R> = [];

    for (let i = 0; i < array.length; i++) {

        // We can guarantee that the result of calling `callback`
        // will be an R, whatever R may be
        const cbResult: R = callback(array[i]);

        // Because we know it's an R, we can safely
        // push it into the array of R!
        ret.push(cbResult);
    }

    // Now the caller of the function has a guarantee that what
    // is returned from `map` is an Array of type R where R is
    // the result type of the callback
    return ret;
}

class Foo {
    constructor(public bar: string) { }
}

const stringArray = ['a', 'b', 'c'];
const numberArray = [1, 2, 3];
const fooArray = [new Foo('hello'), new Foo('friend')];

// By using generic type parameters, we can guarantee the
// type safety of the callback functions
const numberArrayAsStrings: Array<string> = map<number, string>(numberArray, (num) => num.toString());

// Each one of these callbacks knows for a fact that the type T
// will be passed in and that they've promised to return the type R
const stringArrayAsUppercase: Array<string> = map<string, string>(stringArray, (letter) => letter.toUpperCase());

// When we do that, we can write functions like map that do
// complex operations on multiple different types of objects
const fooArrayAsBars: Array<string> = map<Foo, string>(fooArray, (foo) => foo.bar);

因此,当您调用 Array.prototype.map 的这个实现时,map 函数的实现不必知道数组包含什么,但作为消费者的您可以从中获益T 类型的元素将传递到您的回调中,并且 R 类型的数组将作为调用 map.

的结果返回

这意味着,在最简单的用法中,泛型对 API 设计人员非常有用,他们在通常包含泛型类型的数据模型上提供一些抽象操作。然后它们对 API 的使用者很有用,因为 API 提供的任何操作都是可能的,同时您仍然在客户端代码中保持类型安全。

通过上面的 map 函数示例,您可以看到实际上可以映射任何类型的数组,以使用本身为类型的回调函数创建结果类型的新类型安全数组安全。


通常,泛型与接口相结合,以支持对不同类型对象的特定操作。原谅冗长的例子!我不确定是否有好的方法可以给出一个不太冗长的有用示例。

interface IEquatable<T> {
    equals: (other: T) => boolean;
}

interface IComparable<T> extends IEquatable<T> {
    compare: (left: T, right: T) => 1 | 0 | -1;
}

class Bar implements IEquatable<Bar> {
    constructor(public bang: number) { }

    equals(right: Bar) {
        return this.bang === right.bang;
    }
}

class Baz implements IComparable<Baz> {
    constructor(public zim: number) { }

    compare(left: Baz, right: Baz) {
        if (left.zim > right.zim) return 1;
        else if (left.zim === right.zim) return 0;
        else return -1;
    }

    equals(other: Baz) {
        return this.compare(this, other) === 0;
    }
}

/**
 * Sort function that would rely on the result of calling compare
 * each element in the array to sort them according to the comparison
 */
function sort<T extends IComparable<T>>(array: Array<T>): Array<T> {
    // Saving space by not implementing this
    return array;
}

/**
 * Determines whether the arrays have the same contents by sorting
 * the arrays and then calling the arrayEqual function to see if the
 * sorted arrays are equal
 */
function arraySameContents<T extends IComparable<T>>(left: Array<T>, right: Array<T>): boolean {
    if (left.length !== right.length) return false;

    const sortedLeft = sort(left);
    const sortedRight = sort(right);

    return arrayEqual(sortedLeft, sortedRight);
}

/**
 * Compares each element in the left array to the element at the same
 * index in the right array, returning true if all elements are equal
 * and false otherwise
 */
function arrayEqual<T extends IEquatable<T>>(left: Array<T>, right: Array<T>): boolean {
    if (left.length !== right.length) return false;

    for (let i = 0; i < left.length; i++) {
        if (!left[i].equals(right[i])) {
            return false;
        }
    }

    return true;
}

如您所见,通过在我们的数组比较函数和接口中使用泛型类型参数,我们可以保证可以比较的数组类型,而无需了解被比较对象的任何事情,除了他们实现了 compareequals 功能!这是泛型编程威力的一个相对简单的例子。 RxJS 框架是泛型真正发挥作用的一个很好的例子。

外卖练习:你能在声明大致如下所示的 class 上实现 IEquatableIComparable 接口吗?

class EquatableArray<T> extends Array<T> implements IEquatable<T> {

}

在开始为此编写任何代码之前需要考虑的几个问题:类型参数 T 是否需要有任何限制?为了能够为 ComparableArray 编写 equals 函数,我们是否需要了解有关 T 的任何额外信息?如果我们尝试使用我们之前编写的函数 arrayEqualarraySameContents 会怎样?

希望这对您有所帮助!祝你好运!