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

在面向对象的设计中,我们一直追求一种结果,就是良好的复用性,基于这个理念,面向对象的设计中加入了协变与逆变(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>

总结:

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

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

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

面向对象设计——协变与逆变,布布扣,bubuko.com

时间: 2024-10-06 20:50:32

面向对象设计——协变与逆变的相关文章

C# - 协变与逆变

public interface IFication<T>{    void Method1 ( T t );    T Method2();} public class Parent : IFication<Parent>{    public string Car="阿斯顿马丁";  // 父亲具有车子 public void Method1 ( Parent p )    {        Console.WriteLine ( p.Car );    }

协变和逆变

具体可以参考:<Effective Java>PECS 原则 (producser-extends, consumer-super) G[+A]类似一个生产者,提供数据.(大部分情况下称G为容器类型)G[-A] 是一个消费者,主要用来消费数据.(如上的 Equiv[-A] (其实就是个A => Boolean的函数,即Function1[-A, Boolean])) 同理函数的参数为何声明为逆变,返回值为协变就好理解了同理class G[+A]{def fun[B >: A](x:

协变、逆变与不变:数组、泛型、与返回类型

转自:http://blog.csdn.net/yi_Afly/article/details/52071260 1. 前言 之前几篇博文,有些地方涉及到了协变性.逆变性与不变性在Java中的表现,所以这篇博文将重点记录这方面的内容,并辅以JDK源码中的一些实例,加以说明. 2. 定义 这里讨论的协变.逆变与不变都是编程语言中的概念.下面介绍定义: 若类A是类B的子类,则记作A ≦ B.设有变换f(),若: 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性. 当A ≦ B时,有f

C#4.0中的协变和逆变

原文地址 谈谈.Net中的协变和逆变 关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal. 协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变.我承认这句话很绕,如果你也觉得绕不妨往下看看. 如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这

协变和逆变(转载)

前言 个人感觉协变(Covariance)与逆变(Contravariance)是 C# 4 中最难理解的一个特性了,因为 C# 4 用了一个非常直观的语法(in和out关键字),在很多情况下,这似乎很简单,in用于输入的参数,out用于输出的返回值,但事实上不完全如此,比如Method(Action<T> action)(会让人抓狂,一会再说).这也是困扰了我相当久的问题,所以今天打算分享一下我自己的理解. 协变和逆变 我们先引入一些记号,假设 T 和 U 是两个类型,那它们之间会有几种关系

c#协变与逆变心得

在读本文之前请先阅读此文: http://www.cnblogs.com/CLR010/p/3274310.html 对于协变与逆变,始终记住三个原则,记住这三点就一切清晰了! 1.不管协变还是逆变,最终遵循的类型变换是===>子类转换为父类,比如string=>object:即被派生类转为派生类: 2.协变修饰为out,协变时,协变修饰的类型参数只能作为返回值!不能用于输入参数,编译不过去: 3.逆变修饰符为in,逆变时,逆变修饰的类型参数只能作为输入参数!不能用于返回值,编译不过去: //

泛型--协变与逆变(转)

对于泛型的知识,一直比较模糊,现在有机会整理一下,突发发现C#还有很多你不知道的东东,继续.NET FrameWork中泛型的协变与逆变: 1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的. 2. C# 4.0对泛型可变性

泛型的协变和逆变

转载C# 泛型的协变和逆变 1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的. 2. C# 4.0对泛型可变性的支持 在C# 4.0之前,所有的泛型类型都是不变量——即不支持将一个泛型类型替换为另一个泛型类型,即使它们之间

Java进阶知识点2:看不懂的代码 - 协变与逆变

要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那么我们可以在任何时候使用MochaCoffee类的引用去替换Coffee类的引用(重写函数时,形参必须与重写函数完全一致,这是一处列外),而不会引发编译错误(至于会不会引发程序功能错误,取决于代码是否符合里氏替换原则). 简而言之,如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用. 赋值的方式最常见