OOP中的逆变和协变

逆变和协变在存在于强类型语言中,虽然很少提及,但是里面蕴含了面向对象的世界观。感谢和我一起讨论这个问题的人。

这里用了C#、Scala的语法作为示例,其实逆变和协变的概念跟语言本身关系不大,事实也是如此。

一、定义

逆变的参数可以由指定的类型的子类型代替,协变的参数可以由指定类型的父类型代替。

Scala中的逆变声明:Function[-A,+B] ;其中泛型-A为逆变类型,在实例化时,可以使用A类型或者A类的子类型。

二、协变与逆变的用途不同

1.语义

Scala中,函数的原型之一包含Function1[-A,+B],表示一个A类型的输入,B类型的输出(返回),替换的语法为A=>B

这个函数的定义就好像说,我需要类A帮我做一些事情,处理完之后给你一个B。而A可以完成的工作,其子类也应该能够完成,这正是里氏替换原则——父类出现的地方都可以用子类代替。逆变强调功能——“能做什么”。

顺便看下协变,输出为协变,表示我会给你你个B对象,如果B是肉,我当然可以说给了你食物,而食物是肉的父类,恰好是协变。如果使用逆变则说不通。协变强调类型——“是什么”。

2.刀和肉的例子

类型:食品<-肉<-牛肉。武器<-刀<-牛肉刀。(<-表示继承关系:父类<-子类)

情景:继续拿Function1做示例,如果Function1需要一把刀,会生产出肉。大致为Function(A):B普通刀(刀类)会生产出普通肉(肉类),牛肉刀会生产出牛肉。

问题:A的类型?B的类型?如何确定

A的类型可以为刀和牛肉刀,因为牛肉刀也是刀。甚至说刀的子类都能够满足条件——都有刀的功能。从继承来讲刀的子类都是刀。

所以A的类型应该为逆变——-刀(刀和子类)

因为做出的是肉,所以B类型肯定包含肉,但不确定是牛肉。所以我们可以设定返回为肉类型。

对于这个情景,我们对FunctionX的最终定义为:FunctionX(-刀):肉

没有协变?

我们没有看到协变,实际上在C#和Scala中,我们设定一个食品类型  来接收FunctionX的返回值也不会报错。因为所有的返回类型在语言中都被声明为协变了,也就是说实际的定义是FunctionX(-刀):+肉。这么做的原因是:如果我返回了一个肉,那么这个肉一定是食品,我总能用返回类型的父类型代替返回的对象。这种行为也是多态一方面的体现——在运行时改变了引用的实际类型。我认为,这是编译层面上的协变。


三、C#中的例子

ICompareable<in T>强调“可比较”这一功能,是逆变。

IEnumerable<out T>强调的是“可数的”类型,是协变。拿List<T>说明,List<肉>表示“我放了肉在列表里面”,也可以说"我放了食物在列表里面",即可以使用List<食品>代替。但是不能说“我放了牛肉在列表里面”,所以用List<牛肉>代替是不对的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值