经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
DesignPattern系列__04里氏替换原则
来源:cnblogs  作者:本墨  时间:2019/8/7 8:50:29  对本文有异议

1.内容引入——继承体系的思考

在继承中,凡是在父类已经实现的方法,其实算是一种契约或者规范,子类不应该在进行更改(重写);但是,由于这一点不是强制要求,所以当子类进行重写的时候,就会对继承体系产生破坏。
同时,继承带来便利的时候,也有弊端:给程序带来了侵入性,增加了对象之间的耦合性,可移植性低。当你修改基类时,子类都需要进行相应的修改。
那么,如何能够保持继承的优点,同时减少缺点对程序的影响呢?也就是我们要讨论的主角——“里氏替换原则”。

2.里氏替换原则的定义

1.第一种定义

如果对每一个类型为S的对象o1, 都用类型为T的对象o2, 使得以T定义的所有程序P在所有的对象o1都替换为o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

2.第二种定义

所有引用基类的地方都必须能透明地使用其子类进行替换。

第二种类型通俗易懂,就是说,只要父类出现的地方,都应该能用其子类进行替换。 但是反过来却不一定成立,也就是子类所在的地方替换成父类,是不一定成立的。

根据定义,我们总结出以下几点:

1.子类必须实现父类定义的抽象方法。对于父类已经实现的非抽象方法,不应该进行重写。

结合代码理解一下:

  1. public abstract class SuperClass {
  2. public abstract void sayHi();
  3. public void doSomething() {
  4. System.out.println("父类被执行...");
  5. }
  6. //定义一个加法运算函数
  7. public int add(int i, int j) {
  8. int result = i + j;
  9. System.out.println("result = " + result);
  10. return result;
  11. }
  12. }
  13. public class SubClass extends SuperClass {
  14. @Override
  15. public void sayHi() {
  16. System.out.println("子类重写了父类的sayHi方法...");
  17. }
  18. //子类特有的方法
  19. public void selfMethod() {
  20. System.out.println("子类特有的方法");
  21. }
  22. //子类重写了父类的非抽象加法运算方法
  23. @Override
  24. public int add(int i, int j) {
  25. int result = i - j;
  26. System.out.println("result = " + result);
  27. return result;
  28. }
  29. }
  30. public class Client {
  31. public static void main(String[] args) {
  32. SuperClass clazz = new SubClass();
  33. clazz.doSomething();
  34. SubClass subClass = new SubClass();
  35. //子类调用自己的方法
  36. subClass.selfMethod();
  37. SuperClass superClass = (SuperClass) subClass;
  38. superClass.add(11,22);
  39. }
  40. }
  41. //运行结果如下:
  42. 父类被执行...
  43. 子类特有的方法
  44. result = -11

当你继承一个基类时,编译器会要求你来实现基类的抽象方法,否则,会报错。
但是,对于已经实现的方法,编译器便不会强制让你去重写(也不推荐这样做):在上面的demo中,子类重写了父类的add方法,在调用时,出现了错误(基类定义的加法逻辑,被子类重写为了一个减法)。这样子,在父类出现的地方,不能由子类完全替换,违背了“里氏替换原则”。

2.子类可以有自己的方法。

在上面的demo中,子类定义了特有的方法selfMethod(),可以实现其他的业务逻辑。

  1. //子类特有的方法
  2. public void selfMethod() {
  3. System.out.println("子类特有的方法");
  4. }

3.当子类覆盖或实现父类的方法时,前置条件(形参)可以比父类方法的输入参数更宽松。

下来看一下demo:

  1. public class Father {
  2. public Collection doSomething(HashMap map) {
  3. System.out.println("父类被执行...");
  4. return map.values();
  5. }
  6. }
  7. public class Son extends Father {
  8. public Collection doSomething(Map map) {
  9. System.out.println("子类被执行...");
  10. return map.values();
  11. }
  12. }
  13. public class Client1 {
  14. public static void main(String[] args) {
  15. invoke();
  16. }
  17. public static void invoke() {
  18. // Father clazz = new Father();
  19. Son clazz = new Son();
  20. HashMap hashMap = new HashMap();
  21. clazz.doSothing(hashMap);
  22. }
  23. //运行结果:父类执行...

在demo中,基类定义了一个doSomething方法,接受的形参类型为HashMap,子类重载了基类的这个方法,并且将接受的形参类型扩展为Map。但是,我们发现,在父类出现的地方,替换为子类后,运行的结果不变。也就是说,子类对象在方法执行时被替换为父类对象,重载的方法并未被执行。如果想要执行子类的方法,就必须重写,这是正确的。
如果反过来呢?下面通过反证来证明第三点:
当子类的方法中的前置条件比父类小的时候,情况会怎么样呢?
在父类和子类中重新定义这两个方法,符合子类的前置条件小于父类这一条件即可。

  1. //修改父类的方法,前置条件扩大为Map
  2. public Collection doSomething(Map map) {
  3. System.out.println("父类被执行...");
  4. return map.values();
  5. }
  6. //修改子类,前置条件缩小为HashMap
  7. public Collection doSomething(HashMap map) {
  8. System.out.println("子类被执行...");
  9. return map.values();
  10. }

当客户端的invoke()方法中,通过Father类型调用doSomething方法时,运行结果如下:
//运行结果: 父类执行****
将对象换成Son类型时,执行结果时:

  1. public static void invoke() {
  2. // Father clazz = new Father();
  3. Son clazz = new Son();
  4. HashMap hashMap = new HashMap();
  5. clazz.doSothing(hashMap);
  6. }
  7. //执行结果: 子类被执行...

我们看到了,子类并没有重写父类的方法,但是子类的方法被执行了(这是因为调用方法时,优先选择参数类型一致的方法,也就是重载方法,找不到的话才去找类型为形参的基类的方法),但是,这个现象在逻辑上面是错误的。

4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

这一点是重写的要求之一,在这里就用代码展示了。

最佳实践

在前面的示例中,我们在子类SubClass中不小心重写了基类SupClass已经实现的方法add(),导致出现了逻辑错误。

1.在实际的使用中,我们要尽量避免重写父类已经实现的方法。

2.在适当的情况下减少使用继承,多使用聚合、组合、依赖等解决问题。

原文链接:http://www.cnblogs.com/JackHou/p/11301941.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号