1. 程式人生 > 其它 >Typescript中的協變、逆變、雙向協變

Typescript中的協變、逆變、雙向協變

協變(Covariant)、逆變(Contravariant)、雙向協變(Bivariant)並非Typescript所特有,其他結構化語言諸如c#、java等也都擁有該特性。
怎麼理解這個概念呢? 先說說集合、超集、子集(set, superset, subset)

下圖中有兩個集合:脊索動物、哺乳動物。 哺乳動物一定是脊索動物,反之則不一定。 因此我們說脊索動物是哺乳動物的超集,哺乳動物是脊索動物的子集。

哺乳動物一定具有脊索動物的特性,反之則不一定。

協變
協變是指:子集能賦值給其超集。

class Chordate {
    hasSpine(): boolean {
        return true;
    }
}

class Mammal extends Chordate {
    canBreastFeed(): boolean {
        return true;
    }
}

function foo(animal: Chordate){
    animal.hasSpine();
}

foo(new Chordate());
foo(new Mammal());

以上程式碼證明了Typescript支援協變,Mammal是Chordate的子集,方法foo接受引數型別為Chordate,而Mammal例項也能賦值給Chordate引數。

逆變
逆變(Contravariance)與雙變(Bivariance)只針對函式有效。 --strictFunctionTypes 開啟時只支援逆變,關閉時支援雙變。

class Chordate {
    hasSpine(): boolean {
        return true;
    }
}

class Mammal extends Chordate {
    canBreastFeed(): boolean {
        return true;
    }
}

declare let f1: (x: Chordate) => void;
declare let f2: (x: Mammal) => void;

f2=f1;
f1=f2; //Error: Mammal is incompatible with Chordate

協變比較好理解,為什麼函式賦值,只能支援逆變(預設),而不支援協變呢? 請看以下程式碼示例

class Animal {
    doAnimalThing(): void {
        console.log("I am a Animal!")
    }
}

class Dog extends Animal {
    doDogThing(): void {
        console.log("I am a Dog!")
    }
}

class Cat extends Animal {
    doCatThing(): void {
        console.log("I am a Cat!")
    }
}

function makeAnimalAction(animalAction: (animal: Animal) => void) : void {
    let cat: Cat = new Cat()
    animalAction(cat)
}

function dogAction(dog: Dog) {
    dog.doDogThing()
}

makeAnimalAction(dogAction) // TS Error at compilation, since we are trying to use `doDogThing()` to a `Cat`

上述程式碼說明了如果函式賦值支援協變的話,有可能會導致bug

參考:
https://codethrasher.com/post/2019-08-28-type-variance-and-typescript/
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html
https://dev.to/codeozz/how-i-understand-covariance-contravariance-in-typescript-2766