# 類與接口
[之前學習過](../basics/type-of-object-interfaces.md),接口(Interfaces)可以用于對「對象的形狀(Shape)」進行描述。
這一章主要介紹接口的另一個用途,對類的一部分行為進行抽象。
## 類實現接口
實現(implements)是面向對象中的一個重要概念。一般來講,一個類只能繼承自另一個類,有時候不同類之間可以有一些共有的特性,這時候就可以把特性提取成接口(interfaces),用 `implements` 關鍵字來實現。這個特性大大提高了面向對象的靈活性。
舉例來說,門是一個類,防盜門是門的子類。如果防盜門有一個報警器的功能,我們可以簡單的給防盜門添加一個報警方法。這時候如果有另一個類,車,也有報警器的功能,就可以考慮把報警器提取出來,作為一個接口,防盜門和車都去實現它:
```ts
interface Alarm {
alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
```
一個類可以實現多個接口:
```ts
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
```
上例中,`Car` 實現了 `Alarm` 和 `Light` 接口,既能報警,也能開關車燈。
## 接口繼承接口
接口與接口之間可以是繼承關系:
```ts
interface Alarm {
alert(): void;
}
interface LightableAlarm extends Alarm {
lightOn(): void;
lightOff(): void;
}
```
這很好理解,`LightableAlarm` 繼承了 `Alarm`,除了擁有 `alert` 方法之外,還擁有兩個新方法 `lightOn` 和 `lightOff`。
## 接口繼承類
常見的面向對象語言中,接口是不能繼承類的,但是在 TypeScript 中卻是可以的:
```ts
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
```
為什么 TypeScript 會支持接口繼承類呢?
實際上,當我們在聲明 `class Point` 時,除了會創建一個名為 `Point` 的類之外,同時也創建了一個名為 `Point` 的類型(實例的類型)。
所以我們既可以將 `Point` 當做一個類來用(使用 `new Point` 創建它的實例):
```ts
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const p = new Point(1, 2);
```
也可以將 `Point` 當做一個類型來用(使用 `: Point` 表示參數的類型):
```ts
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
function printPoint(p: Point) {
console.log(p.x, p.y);
}
printPoint(new Point(1, 2));
```
這個例子實際上可以等價于:
```ts
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface PointInstanceType {
x: number;
y: number;
}
function printPoint(p: PointInstanceType) {
console.log(p.x, p.y);
}
printPoint(new Point(1, 2));
```
上例中我們新聲明的 `PointInstanceType` 類型,與聲明 `class Point` 時創建的 `Point` 類型是等價的。
所以回到 `Point3d` 的例子中,我們就能很容易的理解為什么 TypeScript 會支持接口繼承類了:
```ts
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface PointInstanceType {
x: number;
y: number;
}
// 等價于 interface Point3d extends PointInstanceType
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
```
當我們聲明 `interface Point3d extends Point` 時,`Point3d` 繼承的實際上是類 `Point` 的實例的類型。
換句話說,可以理解為定義了一個接口 `Point3d` 繼承另一個接口 `PointInstanceType`。
所以「接口繼承類」和「接口繼承接口」沒有什么本質的區別。
值得注意的是,`PointInstanceType` 相比于 `Point`,缺少了 `constructor` 方法,這是因為聲明 `Point` 類時創建的 `Point` 類型是不包含構造函數的。另外,除了構造函數是不包含的,靜態屬性或靜態方法也是不包含的(實例的類型當然不應該包括構造函數、靜態屬性或靜態方法)。
換句話說,聲明 `Point` 類時創建的 `Point` 類型只包含其中的實例屬性和實例方法:
```ts
class Point {
/** 靜態屬性,坐標系原點 */
static origin = new Point(0, 0);
/** 靜態方法,計算與原點距離 */
static distanceToOrigin(p: Point) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
/** 實例屬性,x 軸的值 */
x: number;
/** 實例屬性,y 軸的值 */
y: number;
/** 構造函數 */
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
/** 實例方法,打印此點 */
printPoint() {
console.log(this.x, this.y);
}
}
interface PointInstanceType {
x: number;
y: number;
printPoint(): void;
}
let p1: Point;
let p2: PointInstanceType;
```
上例中最后的類型 `Point` 和類型 `PointInstanceType` 是等價的。
同樣的,在接口繼承類的時候,也只會繼承它的實例屬性和實例方法。
## 參考
- [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html))
---
- [上一章:類](class.md)
- [下一章:泛型](generics.md)