多态(polymorphism),是面向对象三大特征中最难以理解的一个。
PS:飞哥当年就是靠着背“多态”骗到的第一份正儿八经的开发工作
首先它的名字就非常难以理解,飞哥试着用自己的语言对它进行一个解释:
同一个变量,调用“同一个”的方法,可以呈现出多种形态。
PS:JavaScript弱类型/函数式编程语言,不存在多态……
怎么回事呢?我们首先来了解几个语法现象。子类和父类可不可以有签名(名称、参数和返回值)完全相同的方法?这是可以的。
但C#和Java有不同的机制,C#需要在父类方法中添加virtual关键字,将其变成虚方法(Java默认所有方法都是虚方法,不需要/不能添加virtual关键字):
class Person { public virtual void Eat() //注意关键字 virtual { Console.WriteLine("人吃饭"); } }
然后,子类方法用override标记,重写父方法(Java也不需要/不能添加override关键字):
class Student : Person { public override void Eat() //注意关键字 override { Console.WriteLine("学生吃饭"); } }
@想一想@:静态方法可以virtual和override么?
演示:不同的对象,调用不同的方法
结合之前学习的语法:父类装子类,
Person ywq = new Student(); ywq.Eat(); //结果为:学生吃饭就会造成一个问题:ywq究竟该调用哪一个Eat()方法呢?究竟是依变量调用Person的,还是依对象调用Student的?
运行上述代码,你会发现,使用的是Student类的Eat()方法。
语法到此为止,那多态如何体现呢?让我们再将ywq赋值给一个Person对象:
Person ywq = new Student(); ywq.Eat(); //代码第2行,输出:学生吃饭 ywq = new Person(); ywq.Eat(); //代码第4行,输出:人吃饭
对比代码第2行和第4行,同一个对象ywq,调用“同样的”方法Eat(),得到的却是不同的结果——这就是多态。请仔细体会,^_^
总结一下,多态出现的三要素:
此外,子类中可以调用父类的方法。为了避免重名,C#中使用base,Java中使用super表示基类对象(复习:this代表当前)
在override方法中调用其基类方法,是一种非常常见的操作:
class AgileStudent : Student { override void Eat() //继续override { base.Eat(); //base指Student } }
@想一想@:假设你忘了base.,会是怎么一个情景?
其次,可以在多层virtual和override:
class AgileStudent : Student { public override void eat() //继续override { base.eat(); //base仅指Student,不包含父类的父类 } }
我们举例来说明,我们需要实现一个源栈食堂供应午餐(ServeLunch)的方法,里面包含:
这么一些流程。1/3/4都是一样的,但:
怎么办?是不是这样:
static void ServeLunch(string role) { Console.WriteLine("开饭啦……"); if (role == "老师") { new Teacher().Eat(); } else if (role == "学生") { new Student().Eat(); } //else ignore //其他代码…… }
这样的代码问题很多:
怎么优化呢?这就要利用到多态了:
static void ServeLunch(Person person) { Console.WriteLine("开饭啦……"); person.Eat(); //不同的Person对象自然会调用不同的方法 //其他代码…… }
咦?!首先if...else...没了,@想一想@:需求中的“如果...就……”能实现么?
但减少if...else只是多态应用的结果,还不是应用多态的关键。
因为多态的作用就是减少if...else,那是你站得不够高……
我们很多程序员只看到了底层原理,忽视了顶层——驾驭大型项目的能力。造轮子,了不起;但是,能用轮子,能用好成千上万的轮子,也是本事呀!否则,你永远不会明白:航母母舰不就是一艘大船么,有什么了不起?……
PS:过度“吹捧”底层,
多态为我们编程开发,带来的最大的好处,是:让我们抽象的设计成为可能。
什么是抽象?大处着眼、屏蔽细节、归纳总结,就是抽象。
对象我们知道是啥玩意了,但“面向”呢?
什么是面向对象(Object Oriented)中的“面向(Oriented)”?和“面向未来”“面向现代化”里的面向类似,指的是在项目的设计和开发(甚至于测试)中,都应该“以对象为核心以对象为导向”进行。
和面向对象相对应(但不是对立)的是“面向过程”。他们的区别是什么?拿到一个需求,如果你:
这就是面向对象,不考虑具体的实现细节,而是考虑对象的组织调用。
@想一想@:为什么要这样思考?—— 代码规模的增加,人脑的局限:迫不得已
比如:
你是不是就只能管人、管物、管流程?谁谁谁负责采购,谁谁谁负责厨房,各方面如何衔接……还去安排鱼香肉丝怎么切丝怎么上色,行不行?
面向对象的编程也一样,代码规模一上来,你还去关心每一个方法的实现细节是不现实的。
不要觉得你把任务分派给对象就OK了。
当对象也越来越多了之后,对象还需要被抽象(继承和多态),否则大量的类一样会把我们搞得懵逼。
一个人其实最多只能管理七个人。
有了继承和多态,我们就可以通过抽象出一些(数量更少的)基类进行更简化的思考:屏蔽一些具体的细节,进行一种“更高层级”“更大范围”的思考和实践。
就像上面的例子,我们把学生和老师都抽象成“人”,是人就吃饭,至于他们各自具体吃什么,我们在进行“全局思考”时不关心。
理解多态,一种比较好的方法:把写代码的过程想象成是两个人:
比如我们在设计一个军事管理系统,其中有一个环节/需求是:发现敌机,防空系统开始射击。
架构师怎么弄?他就想到,要在之前的代码里,插入这么两行代码:
//其他code FindEnemy(); //发现敌机 airDefense.Shoot(); //有个airDefense,它能够开火就行 //其他code....
airDefense是啥?AirDefense的实例啊,定义这样一个类,就OK啦:
class AirDefense { public virtual void Shoot() { } }
至于防控系统怎么样的开火,我不care……
最后实现的时候,就苦逼小码农上场——架构师的代码,你不要乱动(复习:封装)——那就只有继承+多态了:
class AirDefense { public virtual void Shoot(){}; } class MissleAirDefense : AirDefense { public override void Shoot() { Console.WriteLine("东方一号导弹发射"); } } class FlakAirDefense : AirDefense { public override void Shoot() { Console.WriteLine("高射炮开火"); } }
继承不是为了重用,而是为了多态。理解:这里的多态,就是抽象。
@想一想@:之前的LegThing和MoveThing啥的为什么让我们别扭?
首先,面向对象要映射现实。面向对象的根本目的是为了让代码更加容易被人理解,而现实是我们最容易理解的部分。合理的映射现实,有助于代码的理解。所以我们Cat和Dog继承自Animal更容易让人理解,而不是继承自什么LegThing,^_^
其次,继承本质上体现的是一种“是”的关系。Cat和Dog继承自Animal,体现的是:猫和狗都“是”动物。那为什么他们都是动物呢?更多的是因为他们共同/类似的行为(会跑会叫有生命),而不是属性(有一个脑袋四条腿),在我们面向对象的设计中,这一点至关重要。
——这可能就是初学者最不放心的地方:怎么能不关心呢?!
不关心实现不关心细节,代码功能实现不了一堆bug怎么办?
道理都懂,但是臣妾做不到啊……
做不到就对了。
面向对象对应的是面向过程,他们不说对立的,不是说面向对象了就不能再面向过程了,恰恰相反:面向过程,始终是面向对象的基础。你基础打得不牢,怎么可能建起来气势恢宏的摩天大楼?
还是那句话:有些东西,是要靠时间来堆的!所以,别瞎操心什么“程序猿35岁过后怎么办”,都是扯淡!“键盘敲烂,月薪过万”,同学们还是代码可劲的造吧,^_^
利用继承和多态,实现下列功能:
多快好省!前端后端,线上线下,名师精讲
更多了解 加: