一、初识多态
我最早了解到"多态"这个概念是在今年年初的时候,那时我看了一篇全面介绍JS面向对象语法的文章,其中就提到了"多态"。那篇文章对于多态的解释也很简单,大致就是"不同子类当中有着同名方法,但执行的逻辑,输出的结果是不一样的 。"
JavaScript
class Programmer {
sayName(){
console.log('程序员')
}
}
class FireMan {
sayName(){
console.log('消防员')
}
}
上面这两个类当中的sayName
方法就体现了类的多态性。
说实话,当时我看完文章的感觉是,这个"多态"的概念很简单,同时又有些奇怪。怪就怪在它和文章中的其它内容,例如:继承、静态类等等相比有所不同。它似乎没有特定的语法糖。当然对于这种怪异感当时的我并没有有去深究。
二、网络上对于JS多态的解释
直到最近由于在工作中需要使用到面向对象编程,所以我便开始重新系统性的学习JS 对象、类与面向对象编程相关的知识,正是在这个过程中,"多态"重新进入到了我的视野当中。于是我便开始在网上查阅关于 "JS与多态"的博客,试图去揭开"多态"那神秘的面纱。
作者转引维基百科中对多态的解释:
多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口,或使用一
个单一的符号来表示多个不同的类型。
🐰:这个解释我读起来很抽象暂时没办法搞懂
传统面向对象的多态是由三个前提组成的。 第一:必须有继承(继承是多态的前提)。 第二:必须有重写(只有子类重写了父类的方法,调这个方法表现出来的形态才是不一样的。 第三:必须有父类引用指向子类对象。
🐰:看完这三个条件我是有很多疑惑的。
首先,为什么必须要有继承和重写?
假设如果没有继承只是有几个类当中恰好有同名且结构相似的方法,那么这数据多态吗?当然或许在传统面向对象语言中针对我上面所说的情况是有一定的语法限制的(也就是说可能和继承一样必需要使用类似于extends
这样的语法结构才能实现多态),但是这样又有新的问题,为什么到了JS当中这种语法限制会消失?毕竟JS也是有extends
语法糖的,为什么就没有多态的语法糖呢?
另外,还有一个问题是,这个"父类引用指向子类对象"这个应该如何理解呢?
作者还认为在JS当中,下面这种情况也算是多态的体现:
JavaScript
function sum(m,n){
return m + n
}
sum(20,30)
sum("abc","cba") //党对不同的数据类型执行同一个操作时,表现出来的行为不一样
🐰:这里似乎确实符合之前维基百科的解释:"使用一个单一的符号来表示多个不同的类型" 。 m 和 n 这 两形参既可以是Number
类型也可以是是String
类型。但是上面这个代码又已经脱离了面向对象编程的的范畴,难道"多态"不是面向对象编程中所专有的一个概念吗?如果答案是"否"的话,那就代表着"多态" 似乎更多的是一种思想了。
作者最后又举了一个JS中多态的例子:
JavaScript
function calcArea(foo){
console.log(foo.getArea);
}
let obj1 = {
name:'why',
getArea(){
return 1000
}
}
class Person{
getArea(){
return 100
}
}
var p = new Person()
calcArea(obj1)
calcArea(p)
🐰:这个例子显然就是我之前所提到的那种情况,对象obj1
恰好与类Person
有一个同名方法getArea
,并且这两个方法结构相同,作者认为这也是多态。
那么这就印证了我之前的猜想了,至少在JS当中想要实现多态似乎并不需要遵循上面所提到的三个条件,即使没有"继承"、"重写"、"类"这些元素也能够实现多态,这时候多态其实就是一种思想了,它实际上是不拘泥于任何形式的。
JavaScript中的多态_javascript多态-CSDN博客
之所以对js的"类"加上引号是因为他更多是一种思想,就像"面向对象"同样是一种思想一样。没有人很肯定的认为js是面向对象的,但也没人能反驳,因为面向对象的编程思想在js处完全可以实现,但是具体做法又似乎与C#,java等面向对象语言略有不同。
🐰:这篇文章中提到,JS中的类与面向对象都更多的是一种思想。确实是这样,在JS当中类通过构造函数模拟出来的,类继承则是通过原型链模拟出来的。
而多态的体现,则更为简单,在子类中直接实现同名函数即可覆盖(override)父类函数。JS中没有类似C#中的virtualde 关键字,所有父类函数都可以直接覆盖。
🐰:这里也谈到了许多文章中说的实现多态的方法:"子类中对父类方法的重写"。不过这里提到,如果在C#中实现多态,是要使用关键字的。但是在JS中是不需要的,可以直接改写,这可能是由于JS中的继承是通过原型链实现的,所以对象中的方法可以覆盖原型链上的同名方法。这种差异还挺有趣的。
🍃【何不三连】JS面向对象最后一弹-多态篇(羽化升仙) - 掘金
其实多态最根本的作用就是通过把过程化的条件语句转化为对象的多态性,从而消除这些条件分支语句。
🐰:这个与我之前看过的一个视频中的内容很相似,那个视频中讲到优化条件语句嵌套的方法之一就是"面向对象",看来那里使用的就是多态了。
🐰:这个地方其实就提到之所以在JS当中实现多态十分简单,跟JS是一门弱类型语言有很大的关系。
总结:
看过了网络上这些介绍JS中多态的文章之后,我先做一个小小的总结。
在JS当中多态没有特定的语法,它更多的是一种编程思想。我总结为是有一种"弱语法、弱类型、思想化"的倾向。而这种倾向,很可能与JS语言中"面向对象的思想化"、"弱类型"这两个特点有关。
三、Java中的多态
在对JS当中的面向对象有了更深入的了解之后,我逐渐认识到JS它虽有面向对象的能力,但这种能力似乎更多的是对其他面向对象语言的一种模仿。这使我逐渐萌生出一个疑问,在那些面向对象语言当中"多态"究竟是一个什么样的解释?
【每天一个技术点】多态,多么变态_哔哩哔哩_bilibili
在这个视频中介绍了Java中多态的两个表现:
- 给方法的形参设置为某种类型,但是在调用方法的时候却可以传入各种类型的参数(因为传入的各种实参类型都是形参类型的子类)
- 多个子类继承了同一个父类上的方法,之后再子类上重写了继承到的方法,这样就使得不同的对象调用同一个方法时会执行不同的逻辑
🐰:下面我使用TS来模拟一下Java的语法。
TypeScript
class Shape{
getArea(){
}
}
class Rectangle extends Shape{
getArea(){
return 100
}
}
class Circle extends Shape{
getArea() {
return 200
}
}
var r = new Rectangle()
var c = new Circle()
function calcArea(shape:Shape){
console.log(shape.getArea()); //父类的引用指向子类的对象var shape:Shape = new Rectangle()
}
calcArea(r)
calcArea(c)
基于上述这两个表现又提出了"多态"的两个条件:
- 要有继承
- 对父类的引用指向子类的实例
四、我的结论
在简单的了解了Java中多态的概念又结合之前所有对于JS中多态的解释,我逐渐明白了为什么在JS当中多态表现出一种"弱语法"的倾向。
假设我们把上面所提到的"Java中多态的两个表现"放到JS当中 , 我们会发现 ,想要实现它们是非常简单的,根本无需"两个条件"。
第一种表现,由于JS是弱类型语法,所以在JS当中无需继承就可以给方法传递各种数据类型的参数。
第二种表现,在JS的面向对象语法中,子类可以直接重写所继承到的父类方法(写一个同名方法,覆盖原型链上的父类方法),无需像Java中那样通过@override
语法糖去事先声明。
所以综上所述,由于JS的语言特点,导致移植到其中的"多态"呈现出:与语法脱钩;与类型脱钩;与继承脱钩;甚至与面向对象脱钩的特征。这是一种很有意思的现象,由于不同编程语言之间的不同特点,导致"多态"这一概念在不同语言中呈现出了一定的差异性。这可以说是"橘生淮南则为橘,橘生淮北则为枳"了,或者说这也是一种"多态",多态的多态。