王辉的博客

是什么让我对未知世界始终充满热情?

0%

共变,不变和反变

公变和反变,又称协变和逆变,要深入理解它们,我建议从它们所解决的问题出发,然后再从技术层面看它们是怎么实现的。

用一句话概括,协变和逆变为了更好的写通用算法。优秀的程序员的都是懒的,能一下做完的事情绝对不重复。

咱们举个例子,说有一个豆子交易员,他牵线搭桥,把豆子从生产者交易到消费者手里,然后从中抽一成的佣金。这个算法是通用的,一方面它既适用于普通的豆子生产者也能处理生产特别豆子的,另一方面它既支持豆子消费者又能交易给所有农产品通吃的更广大消费者。

function exchange(producer producer, consumer consumer) {
val bean = producer.getBean();
getMoney(bean.getBeanPrice() * 0.1);
consumer.consume(bean);
}
我们接下来具体看看在什么情况下能用这个算法。现在我们有三个类,分别是父类,农产品(product),中间的豆子 (bean),和子类绿豆 (greenBean)。

先看消费端,对于这个交易员来说,如果来的消费者是农产品通吃,那么给交易员一个consumer,交易可通过。但如果消费者只吃绿豆,那么交易员就做不了这单生意了,因为生产者提供的可能只是普通的豆子。

再看生产端,对于交易员来说,如果来的生产者是产绿豆的,那么给交易员一个producer,交易可通过。但如果生产者给的是一个任意的农产品,交易员就不做这个生意,万一是一个大西瓜,只抽一成的佣金,运来运去赔本不说,也不一定卖的出去,因为一些挑剔的消费者是只吃豆子的。

消费端,看逆变,农产品是豆子的父类,而农产品的消费者确是豆子消费者的子类,consumer可以被当做consumer传给通用的交易算法。生产端,看协变,绿豆是豆子的子类,绿豆的生产者也是豆子生产者的子类,producer可以被当做producer传给通用算法。

这一逆一协中的奥秘就是,要想写出来通用的算法,当你面对的是生产方的时候,条件要严,而当面对的是消费者的时候,条件要松。交易员把好严生产松消费的关,生意就好做,算法写一个就完事。

最后,不同的编程语言有不同的实现方式,不管是java中的super/extends,还是c#中的in, out,还是scala中的+/-,说的都是一个事。如果你还不满足,想挖的更深,这里有一个从范畴论的角度开讲的,http://tomasp.net/blog/variance-explained.aspx/。

微信搜《技术管理者的自我修养》订阅本博客