面向对象包括封装、继承、组合、多态,其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。
但是 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 1 天前
什么叫类似的效果。
|
3
dcsuibian 1 天前 不了解 Go 和 C++,但是 JavaScript/TypeScript 和 Python 和 Java 我还是了解的。
我说直白点,你在其它语言里追求另一种语言的写法是白费工夫 你这个项目的介绍中提到“研究设计模式”,但是《设计模式》书中说过: [img] [/img]程序设计语言的选择非常重要,它将影响人们理解问题的出发点。我们的设计模式采用了 Smalltalk 和 C++层的语言特性,这个选择实际上决定了哪些机制可以方便地实现,哪些则不能。如果采用过程式语言,那么可能就要包括诸如“继承”“封装”和“多态”的设计模式。相应地,一些特殊的面向对象语言可以直接支持我们的某些模式,例如,CLOS 支持多方法概念,这就减少了访问者等模式的必要性。 对于你的这个问题,如果你追求的是核心目标:“同一个调用,根据对象的不同产生不同的行为,可以方便地替换实现方式”,那么我可以跟你说 Python 和 JS 是咋做的 但是你问怎么实现“多态这个效果”,那你追求的不是这个核心目标,而是想复刻 Java 的实现路径 或者说 Java 味太重了。即使你真的实现了,在 js 和 python 中大家也不那么写,没有用 |
4
pursuer 1 天前
毕竟通用编程语言都是图灵完备的,通常要实现什么逻辑都是可以的,只是有的麻烦有的简单。
除了 JS 和 Python 这样的脚本语言自带的 eval ,其他语言通常较少具备对等特性。 |
5
liuliuliuliu PRO |
6
jarryli OP @dcsuibian 谢谢。其实我想通过多态来研究语言特性。经验实践 JS Go Py 等均无法实现或者翻译 Java 这种效果。这正是不同语言设计哲学所带来的特性,也很有趣。
|
7
rb6221 1 天前
为什么要模拟
你举得几个语言都恰好不是 OOP ,或者不完全 OOP 的语言,我都怀疑你是故意立靶子打。 每个语言有自己的特性和使用场景,用到最合适处就没问题了,非要用在不合适处,那我也没话说 |
8
xuyang2 1 天前
> 其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。
裘千尺:“我二十年前就已说过,你公孙家这门功夫难练易破,不练也罢。” |
9
cheng6563 1 天前
父类方法要求传入子类的实例这是什么神奇的多态用法...
|
10
daysv 1 天前
ai 帮我答了一堆,但是好像不能复制 ai 答案。
|
11
AV1 1 天前
有的编程语言没有 overload ,很难模拟出 java 那种多态。
而且 overload 和 override 同时出现的时候,不同编程语言的处理方式都不一样的。 |
12
xtreme1 1 天前 一个函数可以为不同的类型服务, 就是多态.
现有的 oop 机制特别是 jvaver 最大的问题在于: 总是把继承和多态混为一谈. 看你的 repo 里面列了这么多语言, 为啥没有体会到 trait 真的是好东西. 但是类似的东西 jvav 这些老古董里面又没有提供, 而是一股脑使用继承, 而继承的耦合又过于严重, 导致发明了各种奇奇怪怪的设计模式来兜底并恶心人. 主楼的示例代码在类型系统里面属于 coercion/overloading 的 ad hoc polymorphism 和 subtype polymorphism, cpp 实现起来是很轻松的. jvav 的类型表达能力虽然不弱, 但绝对不算强的. |
13
slowman 23 小时 40 分钟前
说实话我以为这是 2010 年的帖子
|
14
jiangzm 22 小时 59 分钟前
这种基础问题 AI 能回答的很好 为啥要发帖
|
15
UnluckyNinja 22 小时 4 分钟前
方法重载八股属于糟粕,算是支持方法重载下对于函数协逆变的实现细节。在动态类型语言里有鸭子类型,而且也基本都不支持方法重载,复现 java 的实现细节没什么意义。如果真想了解多态应该去了解类型系统、协变逆变等
|
16
netabare 18 小时 47 分钟前 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 15 小时 20 分钟前 via Android
|
18
hidemyself 13 小时 24 分钟前 难以想象,写出这种 Java 代码的人,希望用这种方式实现什么样的效果
|
19
florentino 13 小时 19 分钟前
逻辑尽量简单,不要给自己和他人留心智负担,多写几段代码,累不死人
|
20
ll5270 12 小时 50 分钟前
这就是我不用 java 的原因 逆天写法
|
22
qrobot 11 小时 40 分钟前
// 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 11 小时 36 分钟前
@jarryli 所以多态能解决什么问题? 不仅仅不能解决问题,反而增加新增负担。 做 Java 第一堂课就是 Favor composition over inheritance , 还在这里整 extends 就说明 Java 没学好, 或者 阿里味太浓
|
24
qrobot 11 小时 31 分钟前
Extensibility 和 Decoupling 都把项目搞复杂化了, 大部分接口几乎永远不会变化的。 就算变化的接口几乎就是重写。 甚至还要改参数。 所以 Extensibility 和 Decoupling 的意义在于什么? 过度设计的产物。软件开发不可能通过 Extensibility 和 Decoupling 来提升所谓的可维护/可替换/提高灵活性. 实际上这些都是需要人来进行维护的。 软件设计不可能一成不变,而是在重构, 在重构. 在复用在重构。
|