返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Typescript协变与逆变简单理解
  • 203
分享到

Typescript协变与逆变简单理解

Typescript协变与逆变Typescript协变Typescript逆变 2022-11-13 18:11:19 203人浏览 泡泡鱼
摘要

目录1. 协变和逆变简单理解2. 协变举例3. 逆变举例4. 更简单点的理解5. 参考1. 协变和逆变简单理解 先简单说下协变和逆变的理解。 首先,无论协变还是逆变,必然是存在于有继

1. 协变和逆变简单理解

先简单说下协变和逆变的理解。

首先,无论协变还是逆变,必然是存在于有继承关系的类当中,这个应该好理解吧。如果你只有一个类,那没有什么好变的。

其次,无论协变还是逆变,既然是变,那必然是存在不同类之间的对象的赋值,比如子类对象赋值给父类对象,父类对象赋值给子类对象,这样才叫做变。

结合上面两条,我觉得协变和逆变在我的字典中就能定义成:支持子类对象赋值给父类对象的情况称之为协变;反之,支持父类对象赋值给子类对象的情况称之为逆变。

举个栗子,我们先假定我们有这么几个类

class Animal {}
class Dog extends Animal {}
class Greyhound extends Dog {}

那么按照上面的理解,要整出一个示例的话,首先我们这里类的继承关系这个条件有了,其次我们要整出的就是这几个类赋值的情况,那么用实参和形参的方式来demo应该是很不错的选择。

2. 协变举例

那么协变的情况我们可以用代码表示为

class Animal {}
class Dog extends Animal {
    bark(): void {
        console.log("Bark")
    }
}
class Greyhound extends Dog {}
function makeDogBark(dog:Dog) : void {
    dog.bark()
}
let dog: Dog = new Dog();
let greyhound: Greyhound = new Greyhound();
let animal: Animal = new Animal();
makeDogBark(greyhound) // OK。 子类赋值给父类
makeDogBark(animal) // Error。编译器会报错,父类不能赋值给子类

我们如果有面向对象基础的话,相信对上面这段代码不难理解, 子类赋值给父类,即协变的情况,在面向对象编程中是非常常见的,且这是实现语言多态特性的基础。而多态,却又是实现众多设计模式的基础。

3. 逆变举例

当我们将函数作为参数进行传递时,就需要注意逆变的情况。比如下面的makeAnimalAction这个函数,就尝试错误的让一只猫去做出狗吠的动作。

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`

这里作为实参的dogAction函数接受一个Dog类型的参数,而makeAnimalAction的形参animalAction接受一个Dog的父类Animal类型的参数,返回值都是void,那么按照正常的思路,这时应该可以像上面协变的例子一样进行正常的赋值的。

但事实上编译是不能通过的,因为最终makeAnimalAction中的代码会尝试以cat为参数去调用dogAction,然后让一个cat去执行doDogThing。

所以这里我们把函数作为参数传递时,如果该函数里面的参数牵涉到有继承关系的类,就要特别注意下逆变情况的发生。

不过有vscode等代码编辑工具的错误提示支持的话,应该也很容易排除这种错误。

4. 更简单点的理解

我觉得将上面的例子稍微改动下,将makeAnimalAction的形参的类型抽出来定义成一个type,应该会有助于我们理解上面的代码。

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: AnimalAction) : void {
    let cat: Cat = new Cat()
    animalAction(cat)
}
type AnimalAction =  (animal: Animal) => void
type DogAction =  (dog: Dog) => void
let dogAction: DogAction = (dog: Dog) => {
    dog.doDogThing()
}
const animalAction: AnimalAction = dogAction // Error: 和上面一样的逆变导致的错误
makeAnimalAction(animalAction)
  • animalAction(animal: Animal)函数,我们可以将其理解成一个可以让动物做动物都有的动作的函数。因此我们可以传dog、cat或者animal进去作为参数,因为它们都是动物,然后animalAction内部可以调用animal.doAnimalThing方法,但不能调用doCatThing或者doDogThing这些方法,因为这些不是所有动物共有的方法。
  • dogAction(dog: Dog)函数, 同上,我们可以将其理解成一个可以让狗狗做狗狗都有的动作的函数。因此可传dog,greyHound这些狗狗对象作为参数,因为对他们都是狗狗,然后dogAction内部可以调用dog.doDogThing和dog.doAnimalThing, 因为这些都是狗狗共有的动作。但是不能调用dog.doGrenHoundThing,因为这不是狗狗共有的动作,只有狗狗的子类灰狗用欧这样的函数。

以上两个都是协变的情况。下面我们看下逆变所导致的错误那一行。

animalAction = dogAction,如果有C/C++经验的,就可以理解成一个函数指,指向另外一个函数,否则理解成一个函数复制给另外一个函数也可以。

假如这个语句可以执行,那么执行之前,dogAction(dog: Dog)只能接受Dog和GreyHound类型的对象,然后去做狗狗都有的动作。

执行之后,因为现在animalAction指向了dogAction,但是animalAction自身的参数是(animal: Animal),即可以接受所有动物类型的对象。

所以最终这里animalAction就变成了这幅模样(隐隐约约觉得这是理解的关键):

function animalAction(animal: Animal) {
 animal.doDogThing()
}

这很明显就是不合理的嘛!所有狗狗都是动物,但这里反过来就不行,不是所有动物都能做狗狗能做的事情,比如这里传个Cat对象进来,那岂不就是让猫去做狗狗的事情了吗。

而反过来,这里假如我们先定义了animalAction, 然后我们让dogAction = animalAction,这种做法却是可行的。我们看最终dogAction变成

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

即dogAction(dog:Dog)指向了animalAction(animal: Animal), 也就是一个以父类型的对象为参数的函数赋予给了一个以子类型的对象为参数的函数,这和我们协变时候的对象之间的赋值时,只能子对象赋值给父对象的做法是相反的。我想,这应该也是为什么叫做逆变的原因吧。

本来这里在我头脑过的时候感觉应该很容易说清楚的,没有想到写下来的时候还是得写这么一大堆,希望能有帮助吧。

5. 参考

https://dev.to/codeozz/how-i-understand-covariance-contravariance-in-typescript-2766

到此这篇关于Typescript协变与逆变简单理解的文章就介绍到这了,更多相关Typescript协变与逆变内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Typescript协变与逆变简单理解

本文链接: https://lsjlt.com/news/170297.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Typescript协变与逆变简单理解
    目录1. 协变和逆变简单理解2. 协变举例3. 逆变举例4. 更简单点的理解5. 参考1. 协变和逆变简单理解 先简单说下协变和逆变的理解。 首先,无论协变还是逆变,必然是存在于有继...
    99+
    2022-11-13
    Typescript协变与逆变 Typescript协变 Typescript逆变
  • 如何理解TypeScript中的子类型、逆变、协变
    这篇文章主要介绍“如何理解TypeScript中的子类型、逆变、协变”,在日常操作中,相信很多人在如何理解TypeScript中的子类型、逆变、协变问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方...
    99+
    2024-04-02
  • 怎么理解Java中的逆变与协变
    这篇文章主要介绍“怎么理解Java中的逆变与协变”,在日常操作中,相信很多人在怎么理解Java中的逆变与协变问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Java中的逆变与协变”的疑惑有所帮助!接下来...
    99+
    2023-06-02
  • 图文详解C#中的协变与逆变
    目录前言协变和逆变总结前言 这篇文章简单说说C#中的协变和逆变。 在C#编程中,由于存在类型之间的强制转换,很容易会出现所谓的类型可变性说法,存在协变、逆变、不变三种。 就比如前一篇...
    99+
    2024-04-02
  • C#中的协变与逆变小结
    一:什么是协变与逆变 协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,逆变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型 只有泛型接口和泛型委托...
    99+
    2024-04-02
  • C#泛型的逆变协变之个人理解
    一般来说, 泛型的作用就类似一个占位符, 或者说是一个参数, 可以让我们把类型像参数一样进行传递, 尽可能地复用代码。 我有个朋友, 在使用的过程中发现一个问题 IFace<o...
    99+
    2023-05-14
    C#泛型的逆变协变
  • 一文带你了解C#中的协变与逆变
    目录协变协变接口的实现逆变里氏替换原则协变 协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。 比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不...
    99+
    2023-02-26
    C# 协变 逆变 C# 协变 C# 逆变
  • C#中协变与逆变的示例分析
    这篇文章主要介绍了C#中协变与逆变的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一:什么是协变与逆变协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类...
    99+
    2023-06-25
  • C#中的协变与逆变怎么实现
    本篇内容介绍了“C#中的协变与逆变怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言在C#编程中,由于存在类型之间的强制转换,很容易...
    99+
    2023-06-29
  • Java泛型之协变与逆变及extends与super选择
    目录什么是不变什么是协变什么是逆变extends 和 super使用extends还是super呢要了解协变与逆变,首先要引入: 根据 Liskov替换原则,如果C是P的子类,则P可...
    99+
    2024-04-02
  • C#中的协变与逆变接口怎么实现
    今天小编给大家分享一下C#中的协变与逆变接口怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。协变协变概念令人费解,多半...
    99+
    2023-07-05
  • Java泛型之协变、逆变、extends与super选择方法
    今天小编给大家分享一下Java泛型之协变、逆变、extends与super选择方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2023-06-30
  • 浅谈Java中的桥接方法与泛型的逆变和协变
    目录1. 泛型的协变1.1 泛型协变的使用1.2 泛型协变存在的问题1.2.1 Java当中桥接方法的来由1.2.2 为什么泛型协变时,不允许添加元素呢1.2.3 从Java字节码的...
    99+
    2024-04-02
  • 简单理解PHP超级全局变量
    本篇文章给大家带来了关于PHP的相关知识,其中主要介绍了关于超级全局变量的相关内容,超级全局变量是一种特殊的变量类型,是内建的和预定义的,可以从任何范围内访问,不需要执行任何特殊的代码段,下面一起来看一下,希望对大家有帮助。理解PHP超级全...
    99+
    2023-05-14
    PHP
  • 让管理变得更简单(3)
    一、     管理是什么——澄清对管理的一些误解   尽管管理已经成为一个前所未有热门的流行词,但是时至今日管理仍然是一门经常被误读的学科。有些人认为管理就是一种忍受,在一个组织中运用各种方式或条文来对成员进行约束,由此来使组织顺利运行;有...
    99+
    2023-01-31
    简单
  • python的变量和简单数字类型详解
    目录1.变量1.1使用变量名时避免命名错误2.字符串2.1修改字符串大小写的方法2.2合并字符串2.3使用制表符或换行符来添加空白2.4删除空白2.5使用字符串时需要避免语法错误3....
    99+
    2024-04-02
  • 怎么理解C语言与物联网的简单通信协议
    本篇内容介绍了“怎么理解C语言与物联网的简单通信协议”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!用C语言...
    99+
    2024-04-02
  • 深入理解Python变量与常量
    变量是计算机内存中的一块区域,变量可以存储规定范围内的值,而且值可以改变。基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。常量是一块只读的内存区域,常量一旦被初始化就不能被改变。...
    99+
    2022-06-04
    常量 变量 Python
  • Golang协程是什么?简单解析与实例说明
    Golang协程是什么?简单解析与实例说明 Golang是一门由Google开发的开源编程语言,其具有简洁、高效和并发性强的特点。在Golang中,协程(Goroutine)是其独有的...
    99+
    2024-02-29
    golang 协程 实例
  • 浅谈Java成员变量与属性的区别(简单最易懂的解释)
    例一:一个Student pojo类:public class Student{private String name;private int age;public String getName(){ return this.name;}p...
    99+
    2023-05-31
    成员变量 属性 java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作