- TypeScript项目开发实战
- (英)彼得·欧汉龙
- 1272字
- 2025-02-18 09:10:05
1.3.8 使用混入(mixin)组成类型
当学习经典面向对象理论时,会遇到类继承的概念。其思想是我们可以从通用的类创建更加特化的类。举一个比较常见的例子。假设有一个vehicle类,包含车辆的基本信息。从vehicle类可以继承出来一个car类。然后从car类可以继承出来一个sports car类。每层继承都添加了上层类所不具备的功能。
一般来说,这是一个用起来比较简单的概念,但是如果在创建代码时,我们想要把两个或更多看起来不相关的东西组合到一起,会发生什么?下面我们来看一个简单的示例。
在数据库应用程序中,存储一条记录是否被删除的信息(但实际上并不删除该记录),以及存储记录上次更新的时间,是很常见的需求。假如我们在数据库中存储了人员记录。一开始,我们想要在人员的数据实体中维护上述信息,但不把这些信息添加到每个数据实体中,而是创建包含这些信息的一个基类,然后继承该基类:

这种方法的第一个问题是将数据库记录状态的详细信息与实际的记录本身混合在一起。在接下来的几章中,随着我们不断深入地介绍面向对象设计,会不断强化一种思想:像这样混合不同的信息不是一种好的做法,因为这样创建的类要做多项工作,从而使它们不够健壮。第二个问题是如果想添加记录的更新日期,要么将更新日期添加到ActiveRecord,要么创建一个新类来添加更新日期,然后把这个类添加到类层次结构中。前者意味着每个扩展ActiveRecord的类也都将具有更新日期,后者意味着要具有更新日期字段,就也具有删除字段。
虽然继承确实有其用武之地,但是近年来兴起了一种思想:将对象组合起来形成新的对象。这种思想是构建不依赖于继承链的独立元素。仍以Person实现为例,我们将使用混入构建相同的功能。
首先定义一个类型,用作混入的构造函数。可以为这个类型命名任何一个名称,但是针对TypeScript中的混入,已经形成了一种约定,即用下面的类型:

我们可以扩展这个类型定义,创建特化的混入。这种看起来有点奇怪的语法其实是在说,给定任何特定的类型,将使用任何合适的实参创建一个新实例。
记录状态实现如下所示:

通过返回一个扩展了构造函数实现的新类,RecordStatus函数扩展了Constructor类型。我们在该类中添加了Deleted标志。
要合并(或称混入)这两种类型,只需使用下面的代码:

这就让我们能够创建一个具有RecordStatus属性的Person对象。但是,这行代码不会实例化任何对象。要实例化Person对象,方法与实例化其他任何类型一样:

接下来添加一些细节,说明记录上次更新的日期。创建另一个混入,如下所示:

要将其添加到ActivePerson中,需要修改定义来包含Timestamp。将哪个混入放到前面不重要,可以是Timestamp,也可以是RecordStatus:

除了属性,还可以在混入中加入构造函数和方法。我们将修改RecordStatus函数,输出记录被删除的时间。为此,我们将把Deleted属性改为getter方法,然后添加一个新方法来实际执行删除操作:

有一点需要特别注意。像这样使用混入是一种好方法,能够用整洁的方法做一些很有用的操作,但是除非我们把参数限制放松为any,否则不能把它们作为参数传递。这意味着我们不能使用下面这样的代码:

如果查看TypeScript文档中对混入的解释(https://www.typescriptlang.org/docs/handbook/mixins.html),可以看到那里的语法看起来有很大的区别。那种方法有许多固有的限制,所以我们不使用那种方法,而是坚持使用这里介绍的方法。我第一次接触到这种方法是在https://www.typescriptlang.org/docs/handbook/mixins.html。