
面向对象包括封装、继承、组合、多态,其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。
但是 java 里面的多态实现太过于严苛,以致于其他语言都无法完全模拟。请看下面的代码,用其他语言实现的时候总难以达到类似效果。
/* 多态:即同一个行为具有多个不同表现形式或形态的能力。 表现形式为,子类重写父类方法,实现类实现接口方法,子类重写抽象类方法等。 多态三个必要条件:继承、重写、父类引用指向子类对象。多态有效消除类型之间的耦合,并提供灵活的可扩展方案。 本例子简单清晰明了的 Java 多态,能看懂这个例子就懂了什么是多态。 */ // 父类 A class A { public String show(D object) { return ("A and D"); } public String show(A object) { return ("A and A"); } // 默认注释掉。可开关注释测试下 // public String show(B object) { // return ("A and B"); // } } // 子类 B class B extends A { public String show(B object) { return ("B and B"); } public String show(A object) { return ("B and A"); } } // 孙子类 C class C extends B { } // 孙子类 D class D extends B { } // 测试验证 public class PolymorphismSimple { public static void main(String[] args) { // 父类声明自己 A a = new A(); // 父类声明子类 A ab = new B(); // 子类声明自己 B b = new B(); C c = new C(); D d = new D(); // 1) A and A 。b 的类型是 B ,也是 B 的实例,A 里没有 show(B)方法,但有 show(A)方法。B 的父类是 A ,因此定位到 A.show(A)。 System.out.println("1) " + a.show(b)); // 2) A and A 。c 的类型是 C ,也是 C 的实例,C 继承 B ,B 继承 A 。A 里没有 show(C)方法,也没有 show(B)方法,最后指向 A.show(A)。 System.out.println("2) " + a.show(c)); // 3) A and D, d 的类型是 D ,也是 D 的实例,D 继承 B ,B 继承 A 。A 里有 show(D)方法,直接定位到 A.show(D)。 System.out.println("3) " + a.show(d)); // 4) B and A, ab 是 B 的实例,但用 A 声明,即向上转型得到的类型是 A ,运行时才能确定具体该调用哪个方法。 // ab 是 B 的实例对象,但引用类型是 A 。类型是在编译时确定,因此从类型开始定位方法。 // A 类中没有 show(B)方法,但有 show(A)方,因为 A 是 B 的父类,ab 也是 A 的实例,于是定位到 A.show(A)方法。 // 由于 B 是 A 的子类,且 B 重写了 A 的 show(A),A 的方法被覆盖了,于是定位到 B.show(A),这就是动态绑定。 // 虽然 B 中有 show(B)方法,但是因为 ab 的类型是 A ,编译时根据类型定位到 A 的方法,而不是 B 。 // 以下几种可开关打开/注释代码测试下。 // - // 若 A 里有 show(A)和 show(B),B 里有 show(B)有 show(A),则编译时关联到 A.show(B),因 B 覆盖了 A.show(B),动态绑定到 B.show(B)。 // - // 若 A 里有 show(A)和 show(B),B 里无 show(B)有 show(A),则编译时关联到 A.show(B),因 B 无覆盖,则直接调用 A.show(B)。 // - // 若 A 里有 show(A)无 show(B),B 里无 show(B)有 show(A),则编译时关联到 A.show(A),因 B 覆盖了 A.show(A),动态绑定到 B.show(A)。 // - // 若 A 里有 show(A)无 show(B),B 里无 show(A)有 show(B),则编译时关联到 A.show(A),因 B 无覆盖,则直接调用 A.show(A)。 // 查找顺序为:编译时根据引用类型确定所属类 -> 根据重载参数类型定位(类型按子->父->祖逐级往上查找)到类的具体方法(包括继承的方法) -> // 运行时实例对象覆盖(覆盖只有子->父一层)了引用类型的同名方法 -> 定位到实例对象的方法。 System.out.println("4) " + ab.show(b)); // 5) B and A 。ab 是 B 的实例,类型是 A 。从 A 类没找到 show(C)方法,也没找到 A.show(B)方法,找到 A.show(A)方法。A.show(A)被 B.show(A)覆盖,因此调用 B.show(A)。 System.out.println("5) " + ab.show(c)); // 6) A and D 。A 里面有 show(D)的方法,直接定位到。 System.out.println("6) " + ab.show(d)); // 7) B and B 。B 里面有 show(B)的方法,直接定位到。 System.out.println("7) " + b.show(b)); // 8) B and B 。B 没有 show(c)方法,但有 show(B)方法。C 继承自 B ,父类型是 B ,因此调用 B.show(B)。 System.out.println("8) " + b.show(c)); // 9) A and D 。B 中没有 show(D)方法,B 继承 A ,A 里有 show(D), 故调用 A.show(D)方法。 System.out.println("9) " + b.show(d)); // 10) B and A 。父类声明子类,存在向上转型。A 里有 show(A),被 B.show(A)覆盖了,因此定位到 B.show(A)。 System.out.println("10) " + ab.show(a)); } } /** * 测试结果 * 1) A and A * 2) A and A * 3) A and D * 4) B and A * 5) B and A * 6) A and D * 7) B and B * 8) B and B * 9) A and D * 10) B and A */ 1 YanSeven 3 月 18 日 什么叫类似的效果。 |
3 dcsuibian 3 月 18 日 不了解 Go 和 C++,但是 Javascript/TypeScript 和 Python 和 Java 我还是了解的。 我说直白点,你在其它语言里追求另一种语言的写法是白费工夫 你这个项目的介绍中提到“研究设计模式”,但是《设计模式》书中说过: [img] [/img]程序设计语言的选择非常重要,它将影响人们理解问题的出发点。我们的设计模式采用了 Smalltalk 和 C++层的语言特性,这个选择实际上决定了哪些机制可以方便地实现,哪些则不能。如果采用过程式语言,那么可能就要包括诸如“继承”“封装”和“多态”的设计模式。相应地,一些特殊的面向对象语言可以直接支持我们的某些模式,例如,CLOS 支持多方法概念,这就减少了访问者等模式的必要性。 对于你的这个问题,如果你追求的是核心目标:“同一个调用,根据对象的不同产生不同的行为,可以方便地替换实现方式”,那么我可以跟你说 Python 和 JS 是咋做的 但是你问怎么实现“多态这个效果”,那你追求的不是这个核心目标,而是想复刻 Java 的实现路径 或者说 Java 味太重了。即使你真的实现了,在 js 和 python 中大家也不那么写,没有用 |
4 pursuer 3 月 18 日 毕竟通用编程语言都是图灵完备的,通常要实现什么逻辑都是可以的,只是有的麻烦有的简单。 除了 JS 和 Python 这样的脚本语言自带的 eval ,其他语言通常较少具备对等特性。 |
5 liuliuliuliu PRO |
6 jarryli OP @dcsuibian 谢谢。其实我想通过多态来研究语言特性。经验实践 JS Go Py 等均无法实现或者翻译 Java 这种效果。这正是不同语言设计哲学所带来的特性,也很有趣。 |
7 rb6221 3 月 18 日 为什么要模拟 你举得几个语言都恰好不是 OOP ,或者不完全 OOP 的语言,我都怀疑你是故意立靶子打。 每个语言有自己的特性和使用场景,用到最合适处就没问题了,非要用在不合适处,那我也没话说 |
8 xuyang2 3 月 18 日 > 其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。 裘千尺:“我二十年前就已说过,你公孙家这门功夫难练易破,不练也罢。” |
9 cheng6563 3 月 18 日 父类方法要求传入子类的实例这是什么神奇的多态用法... |
10 daysv 3 月 18 日 ai 帮我答了一堆,但是好像不能复制 ai 答案。 |
11 AV1 3 月 18 日 有的编程语言没有 overload ,很难模拟出 java 那种多态。 而且 overload 和 override 同时出现的时候,不同编程语言的处理方式都不一样的。 |
12 xtreme1 3 月 18 日 一个函数可以为不同的类型服务, 就是多态. 现有的 oop 机制特别是 jvaver 最大的问题在于: 总是把继承和多态混为一谈. 看你的 repo 里面列了这么多语言, 为啥没有体会到 trait 真的是好东西. 但是类似的东西 jvav 这些老古董里面又没有提供, 而是一股脑使用继承, 而继承的耦合又过于严重, 导致发明了各种奇奇怪怪的设计模式来兜底并恶心人. 主楼的示例代码在类型系统里面属于 coercion/overloading 的 ad hoc polymorphism 和 subtype polymorphism, cpp 实现起来是很轻松的. jvav 的类型表达能力虽然不弱, 但绝对不算强的. |
13 slowman 3 月 18 日 说实话我以为这是 2010 年的帖子 |
14 jiangzm 3 月 19 日 这种基础问题 AI 能回答的很好 为啥要发帖 |
15 UnluckyNinja 3 月 19 日 方法重载八股属于糟粕,算是支持方法重载下对于函数协逆变的实现细节。在动态类型语言里有鸭子类型,而且也基本都不支持方法重载,复现 java 的实现细节没什么意义。如果真想了解多态应该去了解类型系统、协变逆变等 |
16 netabare 3 月 19 日 via iPhone 什么乱七八糟的…Java 那玩意不就是换个名字的继承。 要说多态最起码也得 adhoc polymorphism 或者 typeclass 那种吧。就更不用提 row polymorphism 或者 effect polymorphism 了。 至于你说的这种动态分派,visitor pattern 就能模拟,那只要一个语言能写 visitor pattern ,就能模拟出来。 Go 、Python 、JS 、C++就不说了,Haskell 都能拿 GADT (不带 typeclass )来模拟。 哦对了,JS 的 prototype 也可以直接运行时「动态替换」,不知道这是不是你要的效果。 |
17 xgdgsc 3 月 19 日 via Android |
18 hidemyself 3 月 19 日 难以想象,写出这种 Java 代码的人,希望用这种方式实现什么样的效果 |
19 florentino 3 月 19 日 逻辑尽量简单,不要给自己和他人留心智负担,多写几段代码,累不死人 |
20 ll5270 3 月 19 日 这就是我不用 java 的原因 逆天写法 |
22 qrobot 3 月 19 日 // 1. 构造函数 function A() {} function B() {} function C() {} function D() {} // 2. 原型链继承 B.prototype = Object.create(A.prototype); B.prototype.cOnstructor= B; C.prototype = Object.create(B.prototype); C.prototype.cOnstructor= C; D.prototype = Object.create(B.prototype); D.prototype.cOnstructor= D; // 3. A 的原型方法 A.prototype.show = function(obj) { if (obj instanceof D) { return "A and D"; } else { return "A and A"; } }; // 4. B 的原型方法(核心修正点) B.prototype.show = function(obj) { // 模拟 Java 的 A ab = new B() 这种引用类型限制 if (this._asTypeA) { // 在 A 的视角里,只有 show(D) 和 show(A) // 传入 b 或 c ,在 A 看来都只能匹配到 show(A) if (obj instanceof D) { return A.prototype.show.call(this, obj); } return "B and A"; // B 重写了 A 的 show(A) } // 模拟 B 自己的视角 (B b = new B()) // B 拥有 show(B), show(A),并继承了 A 的 show(D) if (obj instanceof B && !(obj instanceof D)) { // c 也是 B 的一种,所以进入这里 return "B and B"; } if (obj instanceof A && !(obj instanceof D)) { return "B and A"; } // 剩下的交给父类 A 处理(比如处理 D ) return A.prototype.show.call(this, obj); }; // --- 测试验证 --- var a = new A(); var b = new B(); var c = new C(); var d = new D(); // 模拟 A ab = new B(); var ab = new B(); ab._asTypeA = true; console.log("1) " + a.show(b)); // A and A console.log("2) " + a.show(c)); // A and A console.log("3) " + a.show(d)); // A and D console.log("4) " + ab.show(b)); // B and A (模拟引用类型为 A) console.log("5) " + ab.show(c)); // B and A (模拟引用类型为 A) console.log("6) " + ab.show(d)); // A and D console.log("7) " + b.show(b)); // B and B console.log("8) " + b.show(c)); // B and B console.log("9) " + b.show(d)); // A and D console.log("10) " + ab.show(a)); // B and A 秒了, 下一题 |
23 qrobot 3 月 19 日 @jarryli 所以多态能解决什么问题? 不仅仅不能解决问题,反而增加新增负担。 做 Java 第一堂课就是 Favor composition over inheritance , 还在这里整 extends 就说明 Java 没学好, 或者 阿里味太浓 |
24 qrobot 3 月 19 日 Extensibility 和 Decoupling 都把项目搞复杂化了, 大部分接口几乎永远不会变化的。 就算变化的接口几乎就是重写。 甚至还要改参数。 所以 Extensibility 和 Decoupling 的意义在于什么? 过度设计的产物。软件开发不可能通过 Extensibility 和 Decoupling 来提升所谓的可维护/可替换/提高灵活性. 实际上这些都是需要人来进行维护的。 软件设计不可能一成不变,而是在重构, 在重构. 在复用在重构。 |
25 jarryli OP @qrobot 。这里多态示例里展示的确实很复杂,这只是为了说明 Java 语言多态的原理,并非宣扬多态。其他语言特性不同,是没有必要那么实现的,就好像一个 JS 里 bind 就可以改变上下文的调用者,没必要 java 那么复杂的转型。你的 [/ 4. B 的原型方法(核心修正点)] 实现看起来更复杂,而且显式地把 A B C D 都写进代码里去了。 |
26 AoEiuV020JP 3 月 20 日 模拟语言特性这种行为本身就有点意义不明, 大可让 AI 从你的代码中逆向出功能开发设计文档,然后换个语言重新实现,也许代码质量会有所提升, |
27 jarryli OP @AoEiuV020JP 为了更好地理解不同语言之间的原理与特性,学习探索而已。 |
28 jarryli OP @UnluckyNinja 方法重载还是很有用的,java 、c++ 基本都离不开它,能解决一部分参数类型和个数变化的问题。js 、py 动态类型语言,很灵活,本身支持缺省参数,就没太有必要了。go 设计哲学讲究简洁,干脆连继承都没有。多态并不要求去实现多载,那个不是关键,而是研究的继承与动态绑定机制。 |
29 UnluckyNinja 3 月 21 日 @jarryli #27 没说重载没用,说的是重载“八股”没用 |