首页 > 代码库 > 面向对象设计——协变与逆变

面向对象设计——协变与逆变

        在面向对象的设计中,我们一直追求一种结果,就是良好的复用性,基于这个理念,面向对象的设计中加入了协变与逆变(Covariance and Contravariance)两个概念,我们先来简单了解一下这两个概念。


简介:


协变:由子类向父类方向转变, 用out关键字标识
逆变:由父类向子类方向转变, 用in关键字

举例:Animal是父类,Dog是从Animal继承的子类;如果一个对象的类型是Dog,那么他必然是Animal。有一个获取宠物的方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这是Animal向Dog方向的转变是逆变。如果一个方法要求的返回值是Animal,那么返回Dog的方法肯定是可以满足其返回值要求的,这是Dog向Animal方向的转变是协变。


代码示例:

        以下三个实例,是对协变和逆变在抽象级别的不同的程度上做的不同应用,也是想通过这三个实例,让大家理解,一种技术在不同抽象的层面带来的不同变化,我们到底是牛刀杀鸡,还是多此一举,请大家细细体会!

一. 数组的协变:


<span style="font-size:18px;">Animal[] animalArray = new Dog[]{};</span>

        我们声明的数组数据类型是Animal,而实际上赋值时给的是Dog数据类型的数组,而每一个Dog对象都可以安全的转变为Animal。Dog向Animal方法转变是沿着父子继承方向上的转变就叫做协变,协变的应用在程序代码的设计中是经常使用的!


二. 委托中的协变和逆变


1.委托中的协变


<span style="font-size:18px;">//委托定义的返回值是Animal类型~父类
//获取宠物
public delegate Animal GetPet();

//委托方法实现中的返回值是Dog,是子类
//获取宠物狗
static Dog GetDog(){return new Dog();}

//GetDog的返回值是Dog, Dog是Animal的子类;返回一个Dog肯定就相当于返回了一个Animal;所以下面对委托的赋值是有效的
//获取宠物的委托加入获取狗,这是协变,是合法的
</span><pre name="code" class="csharp"><span style="font-size:18px;">GetPet <span style="font-family: Arial, Helvetica, sans-serif;">getMethod = GetDog; </span></span>


2.委托中的逆变


<span style="font-size:18px;">//委托中的定义参数类型是Dog
//喂养宠物(狗)
public delegate void FeedPet(Dog target);


//实际方法中的参数类型是Animal
//喂养动物
static void FeedAnimal(Animal target){}


// FeedAnimal是FeedDog委托的有效方法,因为委托接受的参数类型是Dog,Dog是可以隐式转变成Animal的,所以委托可以安全的的做类型转换,正确的执行委托方法;
//喂养宠物变为喂养动物,这是逆变,也是合法的
</span><pre name="code" class="csharp"><span style="font-size:18px;">FeedPet <span style="font-family: Arial, Helvetica, sans-serif;">feedDogMethod = FeedAnimal; </span></span>

三. 泛型委托的协变和逆变:


1. 泛型委托中的协变


如下委托声明:
<span style="font-size:18px;">//寻找的泛型委托:out代表可能协变,T为标记符
public delegate T Find<out T>(); </span>

        Find委托要返回一个泛型类型T的实例,在泛型的尖括号中有一个out关键字,该关键字表明T类型是可能要做协变的


<span style="font-size:18px;">//声明Find<Dog>委托
Find<Dog> findDog = ()=>new Dog();
//声明Find<Animal>委托,并将findDog赋值给findAnimal是合法的,类型T从Dog向Animal转变是协变
Find<Animal> findAnimal = findDog; </span>


2. 泛型委托中的逆变

        委托声明:

<span style="font-size:18px;">//喂养的泛型委托:in代表可能逆变,T为标记符
public delegate void Feed<in T>(T target); </span>


        Feed委托接受一个泛型类型T,注意在泛型的尖括号中有一个in关键字,这个关键字的作用是告诉编译器在对委托赋值时类型T可能要做逆变


<span style="font-size:18px;">//先声明一个T为Animal的委托
Feed<Animal> feedAnimalMethod = a=>Console.WriteLine(“Feed animal lambda”);

//将T为Animal的委托赋值给T为Dog的委托变量,这是合法的,因为在定义泛型委托时有in关键字,如果把in关键字去掉,编译器会认为不合法
Feed<Dog> feedDogMethod = feedAnimalMethod; </span>


总结:

        其实协变和逆变不是个新鲜的概念,而以前我们更多使用的是协变,逆变的使用较少,尤其是在泛型和委托中使用,经过这么一番研究,我们发现,这种技术在一定的基础上提高了我们代码的复用性,我们没有必要为每个类都建立一个方法,利用协变和逆变,我们可以做很多复用性的设计!

        这在生活中,其实也好理解,父亲能做的事情,都会教给儿子,所以父亲出现的地方,儿子可以出现,传说中的虎父无犬子,而儿子出现的地方,父亲要选择性出现,因为,父亲将自己会的倾囊相授,儿子却可能还会其他的不一样的东西!

        难怪老师常说,有人的代码就像是堆柴禾,而有人的代码就像是艺术品,这不仅仅是代码工整的问题,而是抽象程度,设计理念的问题了啊!