第 41 讲:面向对象编程(十三):接口的多态
前面我们讲过了多态,不过是针对于类的。在我们讲了接口之后,接口实际上也可以多态。
Part 1 引例
假设我们邀请一群会吃饭的小伙伴参加比赛,然后角逐选出最强的一位参赛选手作为冠军。那么我们就需要书写一个方法,大概是这样的:
注意我们这里,需要传入一系列的会吃饭的小伙伴。如果我们在这里写的是 Animal[]
的话,因为有些小伙伴不一定“会吃饭”(有 Eat
方法),那么我们就无法通过 Animal[]
来完成这个任务。
最合适的办法,那肯定是用 IFoodie[]
来完成。返回值呢?第一名呗。那为什么不返回 Animal
类型而还是用 IFoodie
呢?并不是从 Animal
类派生的才可以实现 IFoodie
接口。猪笼草就不能参赛了么?猪笼草也会吃东西,对吧。可能你问我,猪笼草也不是动物了啊。你看我这个引例给的假设,我可没有说“只能是动物参赛”。
那么,总的来说,我们使用接口类型来表示“能做什么事情”的实例,来参与计算。
可是,我们怎么传参进去呢?还记得类的多态吗?类的多态是通过从某个类派生而产生的。那么,这里我们也可以这么做:
这就是所谓的接口的多态。我们使用实例化语句来产生一个实例,但用的是 IFoodie
接口来接收,因为它们都实现了 IFoodie
接口。
这个
pitcherPlant
是猪笼草。
Part 2 接口类型多态的用法
我们把这些类型都用接口类型来接收了,可这样做的好处是什么呢?
还记得 IFoodie
这个接口类型里有什么东西吗?是的,只有一个 Eat
方法。换句话说,如果我们将某个实例赋值给一个 IFoodie
类型(假设这种赋值是成功的的话),那么后续在使用这个变量的时候,你只能写出 .Eat()
的东西出来,别的都不行。因为这个接口只有这个成员。
假设我们这里的 IFoodie
接口里的 Eat
方法返回一个 int
类型结果,表示这个吃货在等同时间里吃了多少东西进去,那么我们取出最大值作为结果,然后把吃得最多的这个对象给返回出来。可以看到,我们只需要一个 Eat
方法,别的方法也用不上,所以直接这么书写就是合适的。
Part 3 接口的隐式和显式转换
和类的继承一致,接口虽然换了个说法,叫做“实现”,但是实际上本身还是继承的语法和机制。所以,它和类的强制和隐式转换也是一样的规则:
如果从子类型转基接口的话,因为是一定可以转换成功的,因此是隐式转换的;
如果从基接口转子类型的话,因为类型不一定成功转换,因此是使用强制转换的。
想必具体我就不用说明了吧。比如前面的 IFoodie
作为接收方,而等号右侧是那些类型的实体。因为这些类型都是实现了 IFoodie
类型的接口的,所以这样的赋值是隐式转换的。
3-1 is
和 as
,以及转换运算符
和类的多态一样,因为类可以通过别的类(父类)来表达出来,因此它可以达到多态的形式。那么,如果是“大转小”的话,需要用强制转换;而如果是“小转大”的话,直接是隐式转换。这里的“大”和“小”指的是这个类型可以适用的类型的范围。显然,抽象类用来给子类继承,那么抽象类的范围应该是更大的。
接口也是一样的。如果我们要把类型赋值给自己实现了的某个接口类型,那么自然是隐式转换的。因为接口类型可以表达的数据类型更多,毕竟非常多的类型都可以实现接口,但类本身只有一个。
那么反过来,如果要把接口转换回普通的类的话,就拿 IFoodie
来说,既然 Dog
、Cat
这些类型全都实现了 IFoodie
接口,那么反过来我们就无法确定,一个 IFoodie
接口真正表达的是什么数据,因此我们需要使用强制转换。如果转换失败,那么必然就会产生错误。此时我们依然需要使用 is
或者 as
运算符来判别其类型。
或者是
比如这样的形式。
3-2 无法对接口使用自定义类型转换器
如题,我们无法在一个类里书写类似这样的代码:
这样在 C# 里直接就是不允许的。你有想过为什么吗?原因其实很简单,因为类型本身是它实现的那些接口决定的。如果你自定义实现类型转换的话,就破坏了这种转换的体系架构。比如我 dog
对象是 Dog
这个类型的,它实现了 IRunnable
、IFoodie
或者别的什么接口,那么我们自然而然就知道,这些接口类型都是 dog
可以转换过去的接口类型;但是,我假设有一个 ICanSaySomething
的接口来表示“能说话”(注意接口名称前面的 I 一定是 interface 这个单词的缩写,而不是指的“我”),显然狗狗是不能说话的,因此无法对狗狗实现这个接口。
那么既然狗狗无法实现这个接口,就必然不可能允许狗狗往 ICanSaySomething
接口上转换。如果 C# 允许你自定义这样的转换的话,显然就破坏了 C# 面向对象的类型转换系统,因此会导致不稳定。
Part 4 没了?
是的,接口的多态基本上和类的多态是一样的写法,所以也没有什么特别需要讲的东西。
至此,面向对象的内容我们就介绍到这里。下一节我们讲开始新的篇章。