[TOC] >[success] # 多态 ~~~ 1.多态的英文'prolymorphism',也就是'poly(复数)' + 'morph(形态)',字面意思复数形态 2.多态:'同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果',书 里白话文解释是给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别, 给出不同的反馈 2.1 书中给了一个故事的比喻:'家里有一只鸭和一只鸡',当主人发出指令'叫'的时候, 鸭子会'嘎嘎嘎',鸡会'咯咯咯',他们同样是'动物,都可以叫',根据指令发出不同的叫声 2.2 利用代码的思想去分析这个比喻'动物是一个类,这个类有叫的方法','鸡和鸭是继承了动物类' ,你家里的'鸡和鸭是你生成的实例活生生的',你的命令'叫'就好比鸭子类型选会'嘎嘎叫加入合唱团' 这也解释了第一条中,不同对象'鸭和鸡'都让他们叫,但是产生的结果过'咯咯咯'和'嘎嘎嘎' 3.像在静态类型语言中,接口的多种不同的实现方式即为多态,或者重写重载也算 多态一种表现形式,js虽然没有接口的概念但是它有继承的概念,继承后的重写可以 理解成多态,还要注意由于动态类型语言js为例他们有类型,所以它在调用的时候又不关心 你的类型,只要有这个方法即可,这也是动态类型语言在多态和鸭子类型的便捷之处 4.多态在使用的时候可以总结成:'做什么' 和 '谁去做以及怎样去做'分离开,简单的说就是 '不变的事物'和'可能改变的事物分离开' ~~~ >[info] ## 多态详解 >[danger] ##### js 的一段'多态',引出'开放封闭原则' ~~~ 1.下面的代码用多态的概念去分析,同一操作了两个不同对象'鸡和鸭',他们的叫声 产生了'不同的执行结果'一个'嘎嘎'一个'咯咯' 2.下面的代码符合需求但是如果你家里的宠物多了一只狗,你需要去改写'makeSound' 随着家里的成员越来越多(我们的代码需要越来越多),整个'makeSound'方法会越来越 大,修改的地方越多,程序产生错误问题的可能性就越大 3.下面的案例却不符合我们第四条说的分离思想,动物叫是'不变的事物',但是动物叫声 根据不同动物的种类是'可变的',如果将叫这个行为'分割出来',把他们叫后的行为结果 '可变的部分封装起来',这样只需要增加代码就可以了,不需要改变代码 4.刚才说的思想其实是开放封闭原则,其核心的思想是: '软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。' 因此,开放封闭原则主要体现在两个方面: '对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。' '对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。' ~~~ ~~~ function makeSound(animal) { if(animal instanceof Duck){ console.log('嘎嘎嘎') }else if(animal instanceof Chicken){ console.log('咯咯咯') } } // es5 声明类 鸭子类 function Duck(){} // es5 声明类 鸡类 function Chicken() {} const duck = new Duck() // 你家那只活着的鸭子对象 const chicken = new Chicken() // 你家那只活着的鸡对象 //发出指令叫 makeSound(duck) makeSound(chicken) ~~~ >[danger] ##### 将上面的案例改写成真正的多态 ~~~ 1.下面案例的好处,我们将'不可变'和'可变分离',就不会因为家庭里添加了新的 宠物需要修改'makeSound'方法,添加的新成员它们接受到指令的时候自动就会 触发他们'sound'方法 ~~~ ~~~ // 不变的地方就是叫声 function makeSound(animal) { animal.sound() } // ----- 下面案例中 讲可变叫声内具体实践封装起来----- // es5 声明类 鸭子类 function Duck(){} Duck.prototype.sound = function () { console.log('嘎嘎嘎') } // es5 声明类 鸡类 function Chicken() {} Chicken.prototype.sound = function () { console.log('咯咯咯') } const duck = new Duck() // 你家那只活着的鸭子对象 const chicken = new Chicken() // 你家那只活着的鸡对象 //发出指令叫 makeSound(duck) makeSound(chicken) ~~~ >[info] ## 静态类型的多态 -- 类型检查 ~~~ 1.因为js 是动态类型,没有类型检查的说,所以我们不需要关心参数类型 只要参数有这个方法即可,但是随着'TS'的应用前端也需要考虑类型问题 2.下面的案例是以java为例 ~~~ ~~~ public class Duck { // 鸭子类 public void makeSound(){ System.out.println( "嘎嘎嘎" ); } } public class Chicken { // 鸡类 public void makeSound(){ System.out.println( "咯咯咯" ); } } public class AnimalSound { public void makeSound( Duck duck ){ // (1) duck.makeSound(); } } public class Test { public static void main( String args[] ){ AnimalSound animalSound = new AnimalSound(); Duck duck = new Duck(); animalSound.makeSound( duck ); // 输出:嘎嘎嘎 } } ~~~ * 如果传入的鸡实例 ~~~ public class Test { public static void main( String args[] ){ AnimalSound animalSound = new AnimalSound(); Chicken chicken = new Chicken(); animalSound.makeSound( chicken ); // 报错,只能接受Duck类型的参数 } } ~~~ ~~~ 1.根据上面的java 案例要说一个问题,由于类型检查'AnimalSound '类中的'makeSound' 只接受'Duck'类型的导致当我们用'Chicken'的实例当成参数的时候报错 2.但是java有一个特点就是可以向上转型,用书中的解释,当我们看到天上的喜鹊和麻雀 我们会说'一只喜鹊在飞','一只麻雀在飞'。但我们也可以说成'一只鸟在飞',我们把'喜鹊' 和'麻雀'向上转型叫做了'鸟',如果我们的'鸡'和'鸭'都继承了动物类,当我们调用'AnimalSound ' 类中的'makeSound'方法时候,让检查的类型是'动物'即实现'向上转型' public class AnimalSound { public void makeSound( Animal animal ){ // (1) animal.makeSound(); } } 3.这里有个小故事,工作时候我们最近在使用'ts'其中有一段和这个特别类似的代码实现效果, 我看当时规定的传参类型是他们的父类,我变产生了不解,不应该是枚举类型指定能使用的 类么,现在明白了可以利用'向上转型'的改变只检查他们的父类即可形成'继承多态效果' 4.因此可以得到一个结论:js 和java 不同点,js是动态类型语言,它在使用的时候即可表现 成'鸭子'又可以表现成'鸡',他的多态是与生俱来的,不需要像java进行一些其他途径来实现 多态,这也是'pyhon' 为什么提出了'鸭子类型',我么也可以从这些天生就可以鸭子类型的语言 体会到的'好处' ~~~ >[info] ##### 在面向对象中设计时候 -- 多态的作用 ~~~ 1.用书中的话来说,'将过程化的条件分支语句转换为对象的多态性,从而消除这些条件语句' ,像上面的案例我们从if判断是那个类,在决定发出什么声音,到后来真正的多态,我们不关心 你是谁怎么叫,只要你能叫,发出指令你执行就可以 2.书中还举了个<<重构:改善既有代码的设计>>我在这里简单概括这个例子:拍电影的时候, 导演喊开始,演员道具,灯光摄影都要去做自己的事,如果每一次导演喊开始,然后还有一个 个再去指导他们做的事,就会形成大量的if语句,这时候如果这些人都是类,他们将自己行为 封装好,那导演喊开始,是不是就很像多态 ~~~ >[info] ## 书中的地图API 的例子 ~~~ 1.书中给的场景:需要编写地图应用一开始选择谷歌地图,后来因为需求部分需要百度地图, 就可以利用多态的形式 ~~~ ~~~ const googleMap = { show: function () { console.log('开始渲染谷歌地图'); } }; const baiduMap = { show: function () { console.log('开始渲染百度地图'); } }; const renderMap = function (map) { if (map.show instanceof Function) { map.show(); } }; renderMap(googleMap); // 输出:开始渲染谷歌地图 renderMap(baiduMap); // 输出:开始渲染百度地图 // 错误使用多态(过多的if判断) const renderMap = function (type) { if (type=== 'google') { googleMap.show() }else if(type === 'baidu'){ baidu.show() } } ~~~