1. 程式人生 > >OOP中的逆變和協變

OOP中的逆變和協變

ott 做的 協變 padding 我們 add adding 原則 列表

逆變和協變在存在於強類型語言中,盡管非常少提及,可是裏面蘊含了面向對象的世界觀。感謝和我一起討論這個問題的人。

這裏用了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<牛肉>取代是不正確的。

OOP中的逆變和協變