一、里氏替换原则

如果说实现开闭原则的关键步骤就是抽象化,那么基类(父类)和子类的继承关系就是抽象化的具体实现,所以里氏替换原则就是对实现抽象化的具体步骤的规范。即:子类可以扩展基类(父类)的功能,但不能改变父类原有的功能。

定义:一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。

里氏替换原则最核心得一句话就是:子类可以扩展基类(父类)的功能,但不能改变父类原有的功能。它包含着四种含义:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类可以增持自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(即:方法的参数)要比父类方法的输入参数更为宽松。
  4. 当子类的方法实现父类的方法时(重写/重载/实现抽象方法),方法的后置条件(即:返回值)要比父类更为更为严格或者相等。

我们先来做一个简单的计算器的功能,创建一个类SumA,实现一个两数相减的功能reduce()

public class SumA {
// 相减
public int reduce(int a,int b){
return a - b;
}
}

再来创建一个类SumB,增加一个两数相加的功能,并且SumBSumA的子类:

public class SumB extends SumA {
// 相加
public int reduce(int a,int b){
return a + b;
}
}

测试一下:

public static void main(String[] args) {
SumB sumB = new SumB();
System.out.println("5 - 4 = "+sumB.reduce(5,4));
}

结果:

这么看起来结果没有错,那么根据里氏替换原则的定义:一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变

我们来将对象换成SumA的子类SumB的对象再来测试一下:

public static void main(String[] args) {
SumA sumA = new SumB();
System.out.println("5 - 4 = "+sumA.reduce(5,4));
}

结果:

可以看见结果发生了很大的变化,通过仔细查看代码我们发现SumA的两数相减方法reduce()SumB的两数相加方法reduce()名字相同。这么来就可以说SumB重写了SumA中的非抽象方法reduce(),并改变了reduce()方法的行为,使程序发生了很大的漏洞。所以我们来将SumB类进行改造:

public class SumB extends SumA {
// 相加
public int add(int a,int b){
return a + b;
}
}

SumB类中增加一个add()方法,这样一来SumB作为子类,既可以调用自己类中的add()方法,也可以调用父类SumA中的reduce()方法。我们再来测试一下:

public static void main(String[] args) {
SumB sumB = new SumB();
System.out.println("5 - 4 = "+sumB.reduce(5,4));
System.out.println("5 + 4 = "+sumB.add(5,4));
}

当然也有人说,如果非要重写父类的方法该怎么办?我这边建议两个方法:

  1. 将现有的继承关系去掉,让SumASumB类都实现同一个接口Sum类,然后再重写Sum类中的reduce()方法。
  2. SumASumB都继承一个比较通俗的基类(父类),将现有的继承关系去掉,采用依赖、聚合,组合等关系代替。

二、合成复用原则

尽量使用对象组合/聚合,而不是使用继承达到软件复用的目的。可以使系统更加的灵活,降低类与类之间的耦合度,一个类的变化对于其他类来说影响相对较少。

继承我们称之为白箱复用,相当于把实现的细节暴露给子类,组合/聚合 也成为黑箱复用,对类之外的对象是无法获取到实现细节的。

合成复用原则的核心是:复用时要尽量使用组合/聚合关系(关联关系),少用继承

我们先来看一个数据库连接的例子:

// 数据库连接
public class DBConnection { //MySQL数据连接
public String getConnection(){
return "MySQL数据库连接......";
}
}
// 产品类 dao
public class ProductDAO { private DBConnection dbConnection; public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
} public void addProduct(){
String connection = dbConnection.getConnection();
System.out.println("使用【"+connection+"】增加产品");
}
}

DBConnection是一个提供数据库连接的类,目前只支持MySQL数据库连接的方法。某一天,客户要求增加一个Oracle数据库连接的产品,那我们先在DBConnection增加一个getOracleConnection()的方法,再去修改ProductDAO类中的代码?这里且不说已经违反了开闭原则,就是各种代码的复制粘贴也让人心烦的,完全不够简洁、优雅。

我们不用去修改ProductDAO类中的代码,只需要将DBConnection类的代码改动一下:

// 数据库连接
public abstract class DBConnection { //数据库连接方法
public abstract String getConnection();
}

如上面的代码,将DBConnection类改为抽象类,将getConnection()方法改为抽象方法。这样一来,如果我们需要MySQL数据库连接,就增加一个MySQLConnection类来继承DBConnection类:

public class MySQLConnection extends DBConnection {

    @Override
public String getConnection() {
return "MySQL数据库连接......";
}
}

如果我们需要Oracle数据库连接,就增加一个OracleConnection类来继承DBConnection类:

public class OracleConnection extends DBConnection {

    @Override
public String getConnection() {
return "Oracle数据库连接......";
}
}

最后在调用ProductDAO类中的addProduct()方法前,我们只需要调用setDbConnection()方法并传入我们所需要的DBConnection类的子类的对象就可以了。

类图:

最后


设计模式中的七大原则已经讲完了,共有四篇博客,感兴趣的朋友可以去我的博客空间看看。

从下一篇博客开始,我将开始讲解一下Java中常见的以及我们经常用到的一些设计模式,包括工厂模式、代理模式、单例......如果有兴趣的朋友可以继续关注我,让我们一同进步,谢谢!

Java设计模式(4:里氏替换原则和合成复用原则详解的更多相关文章

  1. 设计模式 第一天 UML图,设计模式原则:开闭原则、依赖倒转原则、接口隔离原则、合成复用原则、迪米特法则,简单工厂模式

    1 课程大纲 2 UML的概述 总结: UML unified model language 统一建模语言 一共有十种图: 类图 用例图 时序图 * 对象图 包图 组件图 部署图 协作图 状态图 (最 ...

  2. 想真正了解JAVA设计模式看着一篇就够了。 详解+代码实例

    Java 设计模式   设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结 设计模式分为 23 种经典的模式,根据用途我们又可以分为三大类.分别是创建型模式.结构型模式和行为型模式 列举几种设 ...

  3. 融会贯通——最常用的“合成复用原则”技能点Get

    复用一个类的时候,多使用对象的组合/聚合的关联关系,而不是继承. 之前提到的"依赖倒转原则",是以里氏代换原则为基础的实现开闭原则目标的手段,这一条路线涉及到的是类的继承(包括单继 ...

  4. 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)

    北风设计模式课程---里氏替换原则(Liskov Substitution Principle) 一.总结 一句话总结: 当衍生类能够完全替代它们的基类时:(Liskov Substitution P ...

  5. 学习java设计模式有用吗?懂这六个原则,编程更轻松

    学习java设计模式有用吗?懂这六个原则,编程更轻松 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实 ...

  6. 设计模式课程 设计模式精讲 3-11 合成复用原则coding

    1 课堂概念 1.0 继承关系的选择 1.1 起名 1.2 定义 1.3 组合聚合优缺点 1.4 继承优缺点 1.5 组合聚合区别 2 代码演练 2.1 反例 2.2 正例 3 疑问解答3.1 疑问解 ...

  7. 面象对象设计原则之七:合成复用原则(Composition/Aggregate Reuse Principle, CARP)

    合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下: 合成复用原则(Composite Reuse Princi ...

  8. DesignPattern系列__07合成复用原则

    基本介绍 合成复用原则的核心,就是尽量去使用组合.聚合等方式,而不是使用继承. 核心思想 1.找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起. 2.针对接口编程,而不是 ...

  9. 提高Java代码质量的Eclipse插件之Checkstyle的使用详解

    提高Java代码质量的Eclipse插件之Checkstyle的使用详解 CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代 ...

随机推荐

  1. Linux配置NTP时间服务器(date、hwclock、NTP服务器的配置)

    目录 date命令 hwclock命令 NTP服务的部署 服务端 客户端 date命令 date 命令的作用是查看和设置Linux中的系统日期时间 date                      ...

  2. Python中的时间日期模块(time、datetime)

    目录 Datetime 获取当前时间 获取当前日期 获取当前时间的tuple元组 格式化日期和时间 时间移动 获取两个时间的时间差 时间格式转换 Time 获取距元年(1970.1.1)的秒数 当时时 ...

  3. 子域名查询、DNS记录查询

    目录 子域名信息查询 Layer子域名爆破机 subDomainBrute 利用google查询 HTTP证书查询 DNS记录查询脚本 IP转换为经纬度 利用网页获取对方经纬度信息 首先关于DNS域名 ...

  4. PowerShell-4.API调用以及DLL调用

    PowerShell可以直接调用API,So...这东西完全和cmd不是一回事了... 调用API的时候几乎和C#一样(注意堆栈平衡): 调用MessageBox: $iii = Add-Type - ...

  5. web php wrong nginx config

    web php wrong nginx config 目录 web php wrong nginx config 题目描述 解题过程 信息收集 robots.txt hint.php Hack.php ...

  6. 『动善时』JMeter基础 — 8、JMeter主要元件介绍

    目录 1.测试计划(Test Plan) 2.线程组 3.取样器(sampler) 4.逻辑控制器(Logic Controller) 5.配置元件(Config Element) 6.定时器(Tim ...

  7. 检查dtd和Xschema文件限制下的xml文件是否符合的Java文件

    先来xml文件: 1 <?xml version="1.0" encoding="utf-8"?> 2 <!DOCTYPE orders SY ...

  8. X264码率控制总结1——ABR,CQP,CRF

    1.  X264显式支持的一趟码率控制方法有:ABR, CQP, CRF. 缺省方法是CRF.这三种方式的优先级是ABR > CQP > CRF. if ( bitrate ) rc_me ...

  9. Journey to the future begins

    当提交申请的那一刻,我就更加确认了自己想要走的路,慢慢一路向上,追求自己想要的生活! 2021.5.18 wzb

  10. Jira&Confluence服务器安装

    1.Mysql安装 参考https://confluence.atlassian.com/doc/database-setup-for-mysql-128747.html 创建相应的数据库 CREAT ...