iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 7
1
Software Development

為什麼世界需要Typescript系列 第 7

泛型 - 07

泛型(Generic)

泛型可以讓你避免重複的程式碼, 用一種清晰和抽像的方式來表達程式碼的意圖.

例如: 有一個交換方法, 它只能交換兩個動物(Animal), 但不能交換數字(number)

在Javascript 世界裡, swap 方法沒辦法明確告知外面 items 輸入參數類型只能限制動物(Animal)

function swap(items) {
   var tmp = items[0];
   items[0] = items[1];
   items[1] = tmp;
   ...
}

程式開發人員看到這段程式碼, 只知道這個swap 方法可以拿來 "交換", 不清楚應用系統細節的開發人員就很容易拿去誤用.

如果非要加上限制, 那就只能在裡面動手腳, 加上屬性判斷, 但那也因為 items 是 var 任意型別, 外面的人根本就無法一眼看出那是甚麼類型.

function swap(items) {
   var tmp = items[0];
   if( tmp.AnimalType == undefined) {
      throw Error("只能交換動物!!");
   }
   items[0] = items[1];
   items[1] = tmp;
   ...
}

在Typescript 世界裡, 你只需要改寫成如下

function swap<T>(items: T[]): void {
   var tmp = items[0];
   items[0] = items[1];
   items[1] = tmp;
   ...
}

是一個泛型符號, 表示任何物件都可以放進去.

但我們這個例子是要限制只有動物(Animal)才能放進去. 所以你可以加上 "緊箍咒"

function swap<T extends Animal>(items: T[]): void {
   var tmp = items[0];
   items[0] = items[1];
   items[1] = tmp;
   ...
}

這種行為, 在Typescript 叫做泛型約束

瞧! Typescript 是不是很簡單就做到這件事情?

另外Typescript 也支援多個泛型參數, 就跟 C# 泛型一樣簡單

function process<T, U>(a: T, b: U) {
   ...
}

泛型物件(Generic Class)

毫無懸念Javascript 不支援泛型物件, Typescript 支援泛型物件

class Person<T> {
   data: T;
}

泛型介面(Generic interface)

進一步, Typescript 也支援泛型介面

interface IPerson<T> {
   ...
}

預設泛型類型

在 TypeScript 2.3 版本以上, 更是支援我們可以為泛型中的參數指定預設類型

例如

function createArray<T = Person>(length: number, val: T): Array<T> {
   let result: T[] = [];
   for (let i = 0; i < length; i++) {
      result[i] = value;
   }
   return result;
}

當使用createArray 方法的時候, 如果沒有直接指定 val 類型參數, 從實際 val 參數值中也無法推斷出的時候, 這個默認類型就會起作用.

let result = createArray(100, val);

元組(Tuple)

Javascript 缺乏對元組的語法支持

例如下面的程式碼片段

let color = [255,0,0];

另外, 每當使用其他數據結構代替元組時, 這些替代數據結構的語義值就會降低, 也就降低開發人員對程式碼的閱讀可讀性.

也有Javascript 用Json 表示這樣的異類型別.

let color = {red: 255, green: 0, blue: 0};

但是這仍然也擺脫不了Javascript 沒有強類型的事實.

在Typescript 世界裡, 你可以這樣宣告明確 Tuple

function getColor(): [number,number,number] {
   return [255, 0, 0];
}

我們可以再一次加強語義, 再明確宣告這是個 RGB 型別

type RGB = [number, number, number];
function getColor(): RGB {
   return [255, 0, 0];
}

一旦你在VSCode 編輯時, 不小心漏打一個數字, 它會馬上警告你錯誤.

另外Typescript 也能這樣使用陣列形式快速指定變數值,

var [x, y] = [10, 20];
[x, y] = [y, x];

介面(interface)

軟體開發設計過程當中, 免不了會遇到策略模式(Strategy Pattern)場景.

策略模式 就是在客戶端(Client) 在執行一個方法(function), 在執行的時候決定要使用哪一種邏輯去跑,無論我們去修改/增加邏輯, 都不會影響到客戶端(Client)呼叫使用的方式.

一個典型的例子就是排序sort 演算法, 但我演算法超爛, 這不用排序演算法示範, 有看過"設計模式"的人都知道"鴨子"的故事.

鴨子故事就是主管要求你設計一隻鴨子, 要求你提供鴨子方法quack() 來輸出鴨子叫. 但是主管要求你要準備許多種類的鴨子, 每一種鴨子的叫聲不一樣.

策略模式(Strategy Pattern) 典型就是要求你設計一個介面(Interface).

甚麼你問我說, 如何在Javascript 裡設計一個介面?

function IDuck() {
   this.classType = "IDuck";
   this.quack = function () {
   }
}

在JavaScript 這樣的 prototype 語言中實現介面(Interface)是很彆扭的.

改成Typescript 世界裡...

interface IDuck {
   quack(): void;
}

簡單的三行程式碼搞定一個 IDuck 介面語義說明.

接下來我們用Typescript 實作一隻綠色鴨子呱呱叫

class GreenDuck implements IDuck {
   quack(): void {
      console.log("呱呱叫");
   }
}

再實作紅色鴨子, 這次是嗚嗚叫

class RedDuck implements IDuck {
   quack(): void {
      console.log("嗚嗚叫");
   }
}

然後在客戶端(Client)呼叫 perform 方法

function perform(animal: IDuck): void {
   animal.quack();
}

perform(new GreenDuck());
perform(new RedDuck());

瞧, 這世界有Typescript 真的很美好!

更多的 interface

Typescript 如同C# 語言一樣, 物件(Class)不支援多重繼承(multiple inheritance), 但支援同時多個 interface

interface IPerson {
   say(): void;
}

interface IAnimal {
   walk(): void;
}

class AnimalBase {
}

class Student extends AnimalBase implements IAnimal, IPerson {
   say(): void {
      ...
   } 
   walk(): void {
      ...
   }
}

上例中, Student 物件繼承了 AnimalBase 也實現了 IAnimal 和 IPerson 介面(interface)


上一篇
多載方法 - 06
下一篇
列舉.介面.回呼函數 - 08
系列文
為什麼世界需要Typescript30

尚未有邦友留言

立即登入留言