# 类的扩展
本节讲解基于接口,抽象类技术实现对象扩展的方法。
# 接口
之前章节已介绍可使用接口来描述对象类型,接口相比类,只创建了类型, 并不会有运行时开销。这里着重从 OOP 的角度进一步加深对接口的理解。
# implements
接口是类的骨架 确定了类的核心特性。接口抽象一套标准使不同类之间不用关心各类的具体实现,只需基于接口定义的规范来进行交互。一个现实案例就是 DOM 规范中的 IDL(接口设计语言) 描述 (opens new window) 通过一系列接口定义,确定 DOM 的内在行为。用户无需关心各浏览器厂商的实现差异,只需基于接口定义的规范即可操作 DOM。
在 TypeScript 利用 Interface 申明一系列规则,然后在类中使用 implements 去实现这些接口。
// 抽象文件接口,暴露读写方法
interface FileIO {
// 返回 16 进制的字符串
read(): string;
// 写入 16 进制的字符串
write(data: string): boolean;
}
// 文本读写 IO
class TextFileIO implements FileIO {
read() {
return 'text file';
}
write(data: string) {
return true;
}
}
// 图片文件读写 IO
class ImgFile implements FileIO {
read() {
return 'hex string data';
}
write(data: string) {
return true;
}
}
类可以 implements 多个接口的定义。
interface I1 {
foo(): void;
}
interface I1 {
bar(): void;
}
// C1 同时实现 I1 和 I2 定义的接口
class C1 implements I1, I1 {
foo() {}
bar() {}
}
TIP
注意接口用于说明类暴露的功能,所以接口定义的方法和属性只能为 public,不能设置为 private, protected。详情参见官方文档 interface class types (opens new window)
你可以使用 属性修饰 限制接口定义内容为只读或可选。
interface Hi {
readonly name: string;
sayHi(name?: string): void
}
class HiPerson implements Hi {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name() {
return this._name;
}
sayHi() {
console.log(`hi ${this.name}`)
}
}
let hiPerson = new HiPerson('tom')
hiPerson.sayHi()# 接口的扩展
和类一样接口也支持扩展
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
// 断言字面量对象符合 Square 接口特征,避免检查报错
let square = {} as Square;
square.color = 'blue';
square.sideLength = 10;
示例采用对象字面量形式创建接口而非类。体现了 JavaScript 对象创建的灵活性,你可以根据实际情况灵活组织代码。
# 多重扩展
你可以同时扩展多个接口的特性。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
// 同时扩展 Shape 和 PenStroke
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square1 = {} as Square;
square.color = 'blue';
square.sideLength = 10;
square.penWidth = 5.0;
# 接口扩展类
接口也可以扩展类。
class C11 {
foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
// I11 继承了 C11 的所有方法和属性申明
interface I11 extends C11 {
fooBar(): void;
}
// C12 必须实现 I11 扩充的方法和 C11 的方法和属性
class C12 implements I11 {
foo: string;
bar: string;
constructor(foo: string, bar: string) {
this.foo = foo;
this.bar = bar;
}
fooBar() {}
}
示例中接口继承类会提取类中的方法和属性,类只需实现接口及提取类中对应的方法和属性即可。该机制基于接口协议实现了对依赖类的重写。
当依赖类中包含私有属性和方法时也可基于此机制来扩充特定子类。
class Control {
private state: any;
}
// 定义 SelectableControl 在 Control 类上扩充 select 方法,并保留 state 私有属性
interface SelectableControl extends Control {
select(): void;
}
// 由于需要包含 Control 私有属性 state 子类必须继承 Control 再补充实现接口才可以
class Button extends Control implements SelectableControl {
select() {}
}
// 报错,SelectableControl 包含 Control 私有属性 state 。
// 无法在子类中覆盖 state,必须继承 Control 再实现接口补充方法才生效
class Image implements SelectableControl {
private state: any;
select() {}
}
# 抽象类
由于接口只是类型申明,类必须实现所有接口定义才可创建实例,抽象类结合了接口的约束及复用某些方法的实现的功能。采用 abstract 定义抽象类
abstract class AbstractAnimal {
// 抽象方法,继承的类必须实现
abstract makeSound(): void;
// 抽象属性,继承的类必须实现
abstract type: string;
move(): void {
console.log('roaming the earth...');
}
}
class Tiger extends AbstractAnimal {
type = 'Felidae';
makeSound() {
console.log('roar');
}
}
let t = new Tiger();
t.move();
console.log(t);
// 报错, 抽象类不能实例化
console.log(new AbstractAnimal());
// 抽象类会产生运行时代码
console.log(Animal);
abstract 同样可以修饰,访问器属性。
abstract class Base {
abstract name: string;
abstract get value();
abstract set value(v: number);
}
class Derived extends Base {
name = 'derived';
// 合法设置访问器属性为常规属性
value = 1;
}
# 混入 (mixins)
混入是一种动态向类或对象实例注入方法的技术。有时我们会动态向实例混入多个类的方法和属性,为了避免混入报错采用 extends 类1,类2 {} 的方式申明扩展。
// 定义类 1
class Programer {
code() {
console.log('code');
}
}
class Pianist {
paly() {
console.log('paly music');
}
}
class Student {
name: string;
constructor(name: string) {
this.name = name;
}
}
let tom = new Student('tom');
// 向 tom 实例动态注入 code 和 play 方法
mixinObj(tom, [Programer, Pianist]);
// 通过此申明实现 tom 的类型检查,注意申明顺序不会影响后续检查
// 注意不要忽略最后一个实例对象 {} 否则会报错!!!
// TODO: 为什么此处需要扩展 {}
interface Student extends Programer, Pianist {}
// 此处 tom
tom.code();
tom.paly();
function mixinObj(obj: any, baseCtors: any[]) {
// 混入代码
}
你可以进一步阅读 Support for Mix-in classes (opens new window) 了解更多混入的信息。
# 总结
本章我们学习了如何基于接口和抽象类来进行继承。知识点如下:
- 接口是一种契约,定义类的使用规则。
- 类采用
implements实现接口 - 类可同时继承多个接口的特性
- 接口可以互相扩展
- 接口基于类扩展,该操作会提取类成员的方法和属性申明
- 类采用
- 采用
abstract定义抽象类,子类必须实现抽象类的抽象方法或属性 - 使用
extends 类1,类2 {}申明对象混入