1. 程式人生 > 其它 >06 - 泛型-開課吧學習中心

06 - 泛型-開課吧學習中心

# 泛型



泛型這一講提前了!主要是,學完這節就可以做事了。



## 泛型的概念



泛型, 或者說提取了一類事物的共性特徵的一種抽象。比如說,松樹、柳樹都是樹,在程式裡有3種表達:

- 介面(Interface)
- 繼承(Inheritance)
- 泛型(Generics)

**繼承是一種強表達。**

松樹繼承於樹,松樹同時也是木材。這樣關係的表達,要麼讓松樹多重整合(樹、木材),要麼松樹<-樹<-木材。

無論哪種,增加程式設計複雜度,也加強了**繼承關係的維護成本**(或者說耦合)。這麼看,關係太強,反而不好!

**介面是一種方面(Aspect)描述**。比如松樹可以生長,那麼松樹是:Growable;動植物都可以進化,那麼它們是Evolvable。

一個型別可以擁有多個方面的特性。

**泛型(Generics)**是對共性的提取(不僅僅是描述)。

```ts
class BedMaker<T> {
//....
make(){

}
}


const A = new BedMaker<紅木>()
const B = new BedMaker<桃木>()


```

- 木頭可以製造床,但是不是所有的木頭可以製造床
- 製造床()這個方法,放到木頭類中會很奇怪,因為木頭不僅僅可以製造床
- 同理,讓木頭繼承於“可以製造床”這個介面也很奇怪

奇怪的程式碼展示:

```tsx
class 紅木 implements IMakeBed{
makeBed(){...}
}
```

設計`IMakeBed` 的目標是為了拆分描述事物不同的方面(Aspect),其實還有一個更專業的詞彙——關注點(Interest Point)。拆分關注點的技巧,叫做關注點分離。如果僅僅用介面,不用泛型,那麼關注點沒有做到完全解耦。

**劃重點:**泛型是一種**抽象共性**(本質)的程式設計手段,它允許將**型別作為其他型別的引數**(表現形式),從而**分離不同關注點的實現**(作用)。

Array\<T\> 分離的是資料可以被線性訪問、儲存的共性。Stream\<T\>分離的是資料可以隨著時間產生的共性。Promise\<T\>分離的是資料可以被非同步計算的特性。

## Hello泛型

```tsx
// 一個identity函式是自己返回自己的函式
// 當然可以宣告它是:number -> number
function identity(arg: number): number {
return arg;
}

// 為了讓identity支援更多型別可以宣告它是any
function identity(arg: any): any {
return arg;
}


// any會丟失後續的所有檢查,因此可以考慮用泛型
function identity<Type>(arg: Type): Type {
return arg;
}

let output = identity<string>("MyString")
// 不用顯示的指定<>中的型別
// let output = identity("MyString")

output = 100 // Error
```



- <>叫做鑽石操作符,代表傳入的型別引數



## 泛型類



泛型類的例子。

```tsx
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
// (number, number) -> number
myGenericNumber.add = function (x, y) {
return x + y;
};

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
```

當然推薦將宣告(Declaration)和定義(Definition)寫到一起:

```tsx
class GenericNumber<T> {
zeroValue : T

constructor(v : T){
this.zeroValue = v
}

add(x : T, y : T) {
return x + y
}
}
```



## 泛型約束(Generic Constraints)

下面的程式會報錯:

```tsx
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
// Property 'length' does not exist on type 'Type'.
return arg;
}
```

考慮為arg增加約束:

```tsx
interface Lengthwise {
length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
```

小技巧**keyof 操作符**

可以用keyof關鍵字作為泛型的約束。

```tsx
type Point = { x: number; y: number };
type P = keyof Point;
// P = "x" | "y"
```

如下面這個例子:

```tsx
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}


let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a");
getProperty(x, "m"); // Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
```

為什麼可以這麼做?

對TS而言所有物件的key是靜態的。

```ts
const a = {x : 1, y : 2}
a.z = 3 // Error
```

因為是靜態的,所以可以用`keyof` 操作符求所有的key。如果一個物件的型別是`any` ,那麼keyof就沒有意義了。



## 例項化泛型型別(將類作為引數)

```ts
function create<Type>(c: { new (): Type }): Type {
return new c();
}

create(Foo) // Foo的例項
```

一個不錯的例子:

```tsx
class BeeKeeper {
hasMask: boolean = true;
}

class ZooKeeper {
nametag: string = "Mikle";
}

class Animal {
numLegs: number = 4;
}

class Bee extends Animal {
keeper: BeeKeeper = new BeeKeeper();
}

class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
```



## 總結



思考:什麼時候用介面?什麼時候用泛型?

思考:將型別作為引數傳遞,並例項化有哪些應用場景?

思考:這個程式會不會報錯?

```tsx
function add<T>(a : T, b : T){
return a + b
}
```