当前位置: 首页 > news >正文

软件设计模式原则(四)里氏替换原则

一.定义

        里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 

        总的来说,该原则的核心思想就是在程序当中,如果将一个父类对象替换成它的子类对象后,该程序不会发生异常。这也是该原则希望达到的一种理想状态。

通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

二.原理

        换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别。

        比如,假设有两个类,一个是Base类,另一个是Child类,并且Child类是Base的子类。那么一个方法如果可以接受一个基类对象b的话:method1(Base b)那么它必然可以接受一个子类的对象method1(Child c).

        里氏替换原则是继承复用的基石。只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才能真正的被复用,而衍生类也才能够在基类的基础上增加新的行为。

        但是需要注意的是,反过来的代换是不能成立的,如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。如果一个方法method2接受子类对象为参数的话method2(Child c),那么一般而言不可以有method2(b).

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

不遵循里氏替换原则的后果:

public class A{public int func1(int a, int b){return a-b;}
}
public class B extends A{  public int func1(int a, int b){  return a+b;  }  public int func2(int a, int b){  return func1(a,b)+100;  }  
} 

 

package com.designphilsophy.lsp.version3;public class Client{  public static void main(String[] args){  B b = new B();  System.out.println("100-50="+b.func1(100, 50));  System.out.println("100-80="+b.func1(100, 80));  System.out.println("100+20+100="+b.func2(100, 20));  }  
}  

运行结果如下:

100-50=150
100-80=180
100+20+100=220

        错误的原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。 

 

三.典例 

        中学时期的数学课告诉我们,正方形是一种特殊的长方形,只不过是它的长和宽是一样的,也就是说我们在面向对象里我们应当将长方形设计成父类,将正方形设计成长方形的子类,但是实际上,这样做是错误的,并不符合里氏替换原则~

public class Rectangle {protected long width;protected long height;public void setWidth(long width) {this.width = width;}public long getWidth() {return this.width;}public void setHeight(long height) {this.height = height;}public long getHeight() {return this.height;}
}
public class Square extends Rectangle {public void setWidth(long width) {this.height = width;this.width = width;}public long getWidth() {return width;}public void setHeight(long height) {this.height = height;this.width = height;}public long getHeight() {return height;}
}
public class SmartTest
{/**长方形的长不短的增加直到超过宽*/public void resize(Rectangle r){while (r.getHeight() <= r.getWidth() ){r.setHeight(r.getHeight() + 1);}}
}

        在上边的代码中我们定义了一个长方形和一个继承自长方形的正方形,看着是非常符合逻辑的,但是当我们调用SmartTest类中的resize方法时,长方形是可以的,但是正方形就会一直增大,一直long溢出。按照里氏替换原则,父类可以的地方,换成子类一定也可以,所以上边的这个例子是不符合里氏替换原则的

问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。 

 解决方式是:可以为长方形和正方形规定一个共同的基类~

相关文章:

  • 本地如何使用PHP搭建简单Imagewheel云图床,结合内网穿透实现在外远程访问?
  • SpringCloud | Dubbo 微服务实战——注册中心详解
  • 48.0/图片和多媒体文件的使用(详细版)
  • 2023年【G1工业锅炉司炉】考试及G1工业锅炉司炉作业考试题库
  • docker-ubuntu中基于keepalived+niginx模拟主从热备完整过程
  • k8s的Pod常见的几种调度形式
  • LeetCode56. Merge Intervals
  • 【办公软件】Outlook启动一直显示“正在启动”的解决方法
  • JVM 运行时参数
  • 科技提升安全,基于YOLOv4开发构建商超扶梯场景下行人安全行为姿态检测识别系统
  • docker基本管理和概念
  • XML映射文件(第二种方式执行SQL语句)
  • AI PC行业深度研究报告:AI PC革新端侧AI交互体验
  • AVP对纵向控制ESP(Ibooster)的需求规范
  • spark sql基于RBO的优化
  • 【剑指offer】让抽象问题具体化
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • chrome扩展demo1-小时钟
  • Cookie 在前端中的实践
  • egg(89)--egg之redis的发布和订阅
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • Java,console输出实时的转向GUI textbox
  • JavaScript 基本功--面试宝典
  • JavaScript设计模式系列一:工厂模式
  • JSDuck 与 AngularJS 融合技巧
  • JS变量作用域
  • laravel 用artisan创建自己的模板
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • tab.js分享及浏览器兼容性问题汇总
  • 从零开始在ubuntu上搭建node开发环境
  • 关于字符编码你应该知道的事情
  • 机器学习学习笔记一
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 跳前端坑前,先看看这个!!
  • 我的面试准备过程--容器(更新中)
  • 我看到的前端
  • 小李飞刀:SQL题目刷起来!
  • 交换综合实验一
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • #宝哥教你#查看jquery绑定的事件函数
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • $.each()与$(selector).each()
  • $forceUpdate()函数
  • (06)Hive——正则表达式
  • (2)STM32单片机上位机
  • (4) PIVOT 和 UPIVOT 的使用
  • (AngularJS)Angular 控制器之间通信初探
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (WSI分类)WSI分类文献小综述 2024
  • (二)linux使用docker容器运行mysql
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214