AKS Pod自動彈性伸縮(HPA) 至ACI 實現
介紹
軟體工程中,我們不僅要建立一致的定義良好的API,同時也要考慮可重用性。 元件不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能。
在像C#和Java這樣的語言中,可以使用泛型
來建立可重用的元件,一個元件可以支援多種型別的資料。 這樣使用者就可以以自己的資料型別來使用元件。
泛型之Hello World
下面來建立第一個使用泛型的例子:identity函式。 這個函式會返回任何傳入它的值。 你可以把這個函式當成是 echo
命令。
不用泛型的話,這個函式可能是下面這樣:
function identity(arg: number): number { return arg; }
或者,我們使用any
型別來定義函式:
function identity(arg: any): any {
return arg;
}
使用any
型別會導致這個函式可以接收任何型別的arg
引數,這樣就丟失了一些資訊:傳入的型別與返回的型別應該是相同的。如果我們傳入一個數字,我們只知道任何型別的值都有可能被返回。
因此,我們需要一種方法使返回值的型別與傳入引數的型別是相同的。 這裡,我們使用了 型別變數,它是一種特殊的變數,只用於表示型別而不是值。
function identity<T>(arg: T): T {
return arg;
}
我們給identity添加了型別變數T
T
幫助我們捕獲使用者傳入的型別(比如:number
),之後我們就可以使用這個型別。 之後我們再次使用了 T
當做返回值型別。現在我們可以知道引數型別與返回值型別是相同的了。 這允許我們跟蹤函式裡使用的型別的資訊。
我們把這個版本的identity
函式叫做泛型,因為它可以適用於多個型別。 不同於使用 any
,它不會丟失資訊,像第一個例子那像保持準確性,傳入數值型別並返回數值型別。
我們定義了泛型函式後,可以用兩種方法使用。 第一種是,傳入所有的引數,包含型別引數:
let output = identity<string>("myString"); // type of output will be 'string'
這裡我們明確的指定了T
是string
型別,並做為一個引數傳給函式,使用了<>
括起來而不是()
。
第二種方法更普遍。利用了型別推論 -- 即編譯器會根據傳入的引數自動地幫助我們確定T的型別:
let output = identity("myString"); // type of output will be 'string'
注意我們沒必要使用尖括號(<>
)來明確地傳入型別;編譯器可以檢視myString
的值,然後把T
設定為它的型別。 型別推論幫助我們保持程式碼精簡和高可讀性。如果編譯器不能夠自動地推斷出型別的話,只能像上面那樣明確的傳入T的型別,在一些複雜的情況下,這是可能出現的。
使用泛型變數
使用泛型建立像identity
這樣的泛型函式時,編譯器要求你在函式體必須正確的使用這個通用的型別。 換句話說,你必須把這些引數當做是任意或所有型別。
看下之前identity
例子:
function identity<T>(arg: T): T {
return arg;
}
如果我們想同時打印出arg
的長度。 我們很可能會這樣做:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
如果這麼做,編譯器會報錯說我們使用了arg
的.length
屬性,但是沒有地方指明arg
具有這個屬性。 記住,這些型別變數代表的是任意型別,所以使用這個函式的人可能傳入的是個數字,而數字是沒有 .length
屬性的。
現在假設我們想操作T
型別的陣列而不直接是T
。由於我們操作的是陣列,所以.length
屬性是應該存在的。 我們可以像建立其它陣列一樣建立這個陣列:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以這樣理解loggingIdentity
的型別:泛型函式loggingIdentity
,接收型別引數T
和引數arg
,它是個元素型別是T
的陣列,並返回元素型別是T
的陣列。 如果我們傳入數字陣列,將返回一個數字陣列,因為此時 T
的的型別為number
。 這可以讓我們把泛型變數T當做型別的一部分使用,而不是整個型別,增加了靈活性。
我們也可以這樣實現上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
使用過其它語言的話,你可能對這種語法已經很熟悉了。 在下一節,會介紹如何建立自定義泛型像 Array
一樣。
泛型型別
上一節,我們建立了identity通用函式,可以適用於不同的型別。 在這節,我們研究一下函式本身的型別,以及如何建立泛型介面。
泛型函式的型別與非泛型函式的型別沒什麼不同,只是有一個型別引數在最前面,像函式宣告一樣:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
我們也可以使用不同的泛型引數名,只要在數量上和使用方式上能對應上就可以。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
我們還可以使用帶有呼叫簽名的物件字面量來定義泛型函式:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
這引導我們去寫第一個泛型介面了。 我們把上面例子裡的物件字面量拿出來做為一個介面:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
一個相似的例子,我們可能想把泛型引數當作整個介面的一個引數。 這樣我們就能清楚的知道使用的具體是哪個泛型型別(比如: Dictionary而不只是Dictionary
)。 這樣接口裡的其它成員也能知道這個引數的型別了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
注意,我們的示例做了少許改動。 不再描述泛型函式,而是把非泛型函式簽名作為泛型型別一部分。 當我們使用 GenericIdentityFn
的時候,還得傳入一個型別引數來指定泛型型別(這裡是:number
),鎖定了之後程式碼裡使用的型別。 對於描述哪部分型別屬於泛型部分來說,理解何時把引數放在呼叫簽名裡和何時放在介面上是很有幫助的。
除了泛型介面,我們還可以建立泛型類。 注意,無法建立泛型列舉和泛型名稱空間。
泛型類
泛型類看上去與泛型介面差不多。 泛型類使用( <>
)括起泛型型別,跟在類名後面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
GenericNumber
類的使用是十分直觀的,並且你可能已經注意到了,沒有什麼去限制它只能使用number
型別。 也可以使用字串或其它更復雜的型別。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
與介面一樣,直接把泛型型別放在類後面,可以幫助我們確認類的所有屬性都在使用相同的型別。
我們在類那節說過,類有兩部分:靜態部分和例項部分。 泛型類指的是例項部分的型別,所以類的靜態屬性不能使用這個泛型型別。
泛型約束
你應該會記得之前的一個例子,我們有時候想操作某型別的一組值,並且我們知道這組值具有什麼樣的屬性。 在 loggingIdentity
例子中,我們想訪問arg
的length
屬性,但是編譯器並不能證明每種型別都有length
屬性,所以就報錯了。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
相比於操作any所有型別,我們想要限制函式去處理任意帶有.length
屬性的所有型別。 只要傳入的型別有這個屬性,我們就允許,就是說至少包含這一屬性。 為此,我們需要列出對於T的約束要求。
為此,我們定義一個介面來描述約束條件。 建立一個包含 .length
屬性的介面,使用這個介面和extends
關鍵字來實現約束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
現在這個泛型函式被定義了約束,因此它不再是適用於任意型別:
loggingIdentity(3); // Error, number doesn't have a .length property
我們需要傳入符合約束型別的值,必須包含必須的屬性:
loggingIdentity({length: 10, value: 3});
在泛型約束中使用型別引數
你可以宣告一個型別引數,且它被另一個型別引數所約束。 比如,現在我們想要用屬性名從物件裡獲取這個屬性。 並且我們想要確保這個屬性存在於物件 obj
上,因此我們需要在這兩個型別之間使用約束。
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
在泛型裡使用類型別
在TypeScript使用泛型建立工廠函式時,需要引用建構函式的類型別。比如,
function create<T>(c: {new(): T; }): T {
return new c();
}
一個更高階的例子,使用原型屬性推斷並約束建構函式與類例項的關係。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!