在面向对象的设计中,我们一直追求一种结果,就是良好的复用性,基于这个理念,面向对象的设计中加入了协变与逆变(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>
总结:
其实协变和逆变不是个新鲜的概念,而以前我们更多使用的是协变,逆变的使用较少,尤其是在泛型和委托中使用,经过这么一番研究,我们发现,这种技术在一定的基础上提高了我们代码的复用性,我们没有必要为每个类都建立一个方法,利用协变和逆变,我们可以做很多复用性的设计!
这在生活中,其实也好理解,父亲能做的事情,都会教给儿子,所以父亲出现的地方,儿子可以出现,传说中的虎父无犬子,而儿子出现的地方,父亲要选择性出现,因为,父亲将自己会的倾囊相授,儿子却可能还会其他的不一样的东西!
难怪老师常说,有人的代码就像是堆柴禾,而有人的代码就像是艺术品,这不仅仅是代码工整的问题,而是抽象程度,设计理念的问题了啊!