
面向对象包括封装、继承、组合、多态,其中最不好理解,也最强悍的就是多态。可以说掌握了多态就掌握了面向对象以及设计模式的精髓。
但是 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 小时 34 分钟前 什么叫类似的效果。 |
3 dcsuibian 53 分钟前 不了解 Go 和 C++,但是 Javascript/TypeScript 和 Python 和 Java 我还是了解的。 我说直白点,你在其它语言里追求另一种语言的写法是白费工夫 你这个项目的介绍中提到“研究设计模式”,但是《设计模式》书中说过: [img] [/img]程序设计语言的选择非常重要,它将影响人们理解问题的出发点。我们的设计模式采用了 Smalltalk 和 C++层的语言特性,这个选择实际上决定了哪些机制可以方便地实现,哪些则不能。如果采用过程式语言,那么可能就要包括诸如“继承”“封装”和“多态”的设计模式。相应地,一些特殊的面向对象语言可以直接支持我们的某些模式,例如,CLOS 支持多方法概念,这就减少了访问者等模式的必要性。 对于你的这个问题,如果你追求的是核心目标:“同一个调用,根据对象的不同产生不同的行为,可以方便地替换实现方式”,那么我可以跟你说 Python 和 JS 是咋做的 但是你问怎么实现“多态这个效果”,那你追求的不是这个核心目标,而是想复刻 Java 的实现路径 或者说 Java 味太重了。即使你真的实现了,在 js 和 python 中大家也不那么写,没有用 |
4 pursuer 52 分钟前 毕竟通用编程语言都是图灵完备的,通常要实现什么逻辑都是可以的,只是有的麻烦有的简单。 除了 JS 和 Python 这样的脚本语言自带的 eval ,其他语言通常较少具备对等特性。 |
5 liuliuliuliu PRO |
6 jarryli OP @dcsuibian 谢谢。其实我想通过多态来研究语言特性。经验实践 JS Go Py 等均无法实现或者翻译 Java 这种效果。这正是不同语言设计哲学所带来的特性,也很有趣。 |
7 rb6221 7 分钟前 为什么要模拟 你举得几个语言都恰好不是 OOP ,或者不完全 OOP 的语言,我都怀疑你是故意立靶子打。 每个语言有自己的特性和使用场景,用到最合适处就没问题了,非要用在不合适处,那我也没话说 |