"泛型", 計算機程式設計中, 一個必不可少的概念。
簡單理解泛型
什麼是泛型
泛型是程式設計語言的一種特性。通過引數化型別來實現在同一份程式碼上操作多種資料型別。
對於強型別語言來書, 提到引數,最熟悉不過的就是定義 function A 時有形參,然後呼叫 A 時傳遞實參。 指定一個表示型別的變數,用它來代替某個實際的型別用於程式設計,而後通過實際呼叫時傳入型別 來對其進行替換,以達到一段使用泛型程式可以實際適應不同型別的目的。
注: 各種程式設計語言和其編譯器、執行環境對泛型的支援均不一樣
泛型解決的問題
- 可重用性
- 型別和演算法安全
- 效率
這是非泛型類和非泛型方法無法具備的
常見的情形
你有一個函式,它帶有一個引數,引數型別是A,然而當引數型別改變成B的時候,你不得不復制這個函式
例如,下面的程式碼中第二個函式就是複製第一個函式——它僅僅是用String型別代替了Integer型別
func areIntEqual(x: Int, _ y: Int) -> Bool {
return x == y
}
func areStringsEqual(x: String, _ y: String) -> Bool {
return x == y
}
areStringsEqual("ray", "ray") // true
areIntEqual(1, 1) // true
複製程式碼
通過採用泛型,可以合併這兩個函式為一個並同時保持型別安全。下面是程式碼實現
// 用一個通用的資料型別T來作為一個佔位符,等待在例項化時用一個實際的型別來代替
func areTheyEqual(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
複製程式碼
JavaScript和泛型的對應關係
泛型 和 模板方法(設計)模式
在一個系列的行為中,有一些是確定的,有一些是不明確的,我們把確定的行為定義在一個抽象類中, 不確定的行為定義為抽象方法,由具體的子類去實現,這種不影響整個流程,但可以應對各種情況的方法 就可以稱之為模板方法模式
demo - Coffee or Tea
幾個步驟:
- 把水煮沸
- 用沸水浸泡茶葉
- 把茶水倒進杯子
- 加檸檬
/* 抽象父類:飲料 */
var Beverage = function(){};
Beverage.prototype.boilWater = function() {
console.log("把水煮沸");
};
Beverage.prototype.brew = function() {
throw new Error("子類必須重寫brew方法");
};
Beverage.prototype.pourInCup = function() {
throw new Error("子類必須重寫pourInCup方法");
};
Beverage.prototype.addCondiments = function() {
throw new Error("子類必須重寫addCondiments方法");
};
/* 模板方法 */
Beverage.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
/* ------------分割線------------ */
/* 實現子類 Coffee*/
var Coffee = function(){};
Coffee.prototype = new Beverage();
// 重寫非公有方法
Coffee.prototype.brew = function() {
console.log("用沸水沖泡咖啡");
};
Coffee.prototype.pourInCup = function() {
console.log("把咖啡倒進杯子");
};
Coffee.prototype.addCondiments = function() {
console.log("加牛奶");
};
var coffee = new Coffee();
coffee.init();
/* 實現子類 Tea*/
var Tea = function(){};
Tea.prototype = new Beverage();
// 重寫非公有方法
Tea.prototype.brew = function() {
console.log("用沸水沖泡茶葉");
};
Tea.prototype.pourInCup = function() {
console.log("把茶倒進杯子");
};
Tea.prototype.addCondiments = function() {
console.log("加檸檬");
};
var tea = new Tea();
tea.init();
複製程式碼
這裡的Beverage.prototype.init就是所謂的模板方法
它作為一個演算法的模板指導子類以何種順序去執行哪些方法,在其內部,演算法內的每一 個步驟都清楚的展示在我們眼前
泛型 和 TypeScript
- 泛型函式
- 泛型類
TypeScript 為 JavaScriopt 帶來了強型別特性,但這就意味著限制了型別的自由度。同一段程式, 為了適應不同的型別,就可能需要寫不同的處理函式
而且這些處理函式中所有邏輯完全相同,唯一不同的就是型別——這嚴重違反抽象和複用程式碼的原則
泛型函式
js原始碼
var service = {
getStringValue: function() {
return "a string value";
},
getNumberValue: function() {
return 20;
}
};
function middleware(value) {
console.log(value);
return value;
}
var sValue = middleware(service.getStringValue());
var nValue = middleware(service.getNumberValue());
複製程式碼
ts改寫使用泛型
const service = {
getStringValue(): string {
return "a string value";
},
getNumberValue(): number {
return 20;
}
};
// 泛型方法改造
function middleware<T>(value: T): T {
console.log(value);
return value;
}
var sValue = middleware(service.getStringValue());
var nValue = middleware(service.getNumberValue());
複製程式碼
middleware 後面緊接的 表示宣告一個表示型別的變數,Value: T 表示宣告引數是 T 型別的, 後面的 : T 表示返回值也是 T 型別的
到這裡為止, TS改造之後的泛型方法和改造之前的js程式碼沒什麼區別。 現在的問題是 middleware 要怎麼樣定義才既可能返回 string,又可能返回 number,而且還能被型別檢查正確推匯出來? 如果不使用泛型方法要實現這個功能的程式碼實現:
第 1 個辦法,用 any:
function middleware(value: any): any {
console.log(value);
return value;
}
複製程式碼
這個辦法可以檢查通過。但它的問題在於 middleware 內部失去了型別檢查,在後在對 sValue 和 nValue 賦值的時候, 也只是當作型別沒有問題。簡單的說,是有“假裝”沒問題
第 2 個辦法,多個 middleware:
function middleware1(value: string): string { ... }
function middleware2(value: number): number { ... }
複製程式碼
或者用 TypeScript 的過載(overload)來實現
function middleware(value: string): string;
function middleware(value: number): number;
function middleware(value: any): any {
// 實現一樣沒有嚴格的型別檢查
}
複製程式碼
這種方法最主要的一個問題是……如果我有 10 種型別的資料,就需要定義 10 個函式(或過載), 那 20 個,200 個呢……
泛型類
即在宣告類的時候宣告泛型,那麼在類的整個作用域範圍內都可以使用宣告的泛型型別, 多數 時候是應用於容器類
背景: 假設我們需要實現一個 FilteredList,我們可以向其中 add()(新增) 任意資料, 但是它在新增的時候會自動過濾掉不符合條件的一些,最終通過 get all() 輸出所有符合條件的資料(陣列)。 而過濾條件在構造物件的時候,以函式或 Lambda 表示式提供
// 宣告泛型類,型別變數為 T
class FilteredList<T> {
// 宣告過濾器是以 T 為引數型別,返回 boolean 的函式表示式
filter: (v: T) => boolean;
data: T[];
constructor(filter: (v: T) => boolean) {
this.filter = filter;
}
add(value: T) {
if (this.filter(value)) {
this.data.push(value);
}
}
get all(): T[] {
return this.data;
}
}
// 處理 string 型別的 FilteredList
const validStrings = new FilteredList<string>(s => !s);
// 處理 number 型別的 FilteredList
const positiveNumber = new FilteredList<number>(n => n > 0);
複製程式碼
甚至還可以把 (v: T) => boolean 宣告為一個型別,以便複用:
type Predicate<T> = (v: T) => boolean;
class FilteredList<T> {
filter: Predicate<T>;
data: T[];
constructor(filter: Predicate<T>) { ... }
add(value: T) { ... }
get all(): T[] { ... }
}
複製程式碼