Java设计模式(4:里氏替换原则和合成复用原则详解
一、里氏替换原则
如果说实现开闭原则的关键步骤就是抽象化,那么基类(父类)和子类的继承关系就是抽象化的具体实现,所以里氏替换原则就是对实现抽象化的具体步骤的规范。即:子类可以扩展基类(父类)的功能,但不能改变父类原有的功能。
定义:一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
里氏替换原则最核心得一句话就是:子类可以扩展基类(父类)的功能,但不能改变父类原有的功能。它包含着四种含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类可以增持自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即:方法的参数)要比父类方法的输入参数更为宽松。
- 当子类的方法实现父类的方法时(重写/重载/实现抽象方法),方法的后置条件(即:返回值)要比父类更为更为严格或者相等。
我们先来做一个简单的计算器的功能,创建一个类SumA,实现一个两数相减的功能reduce():
public class SumA {
// 相减
public int reduce(int a,int b){
return a - b;
}
}
再来创建一个类SumB,增加一个两数相加的功能,并且SumB是SumA的子类:
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));
}

当然也有人说,如果非要重写父类的方法该怎么办?我这边建议两个方法:
- 将现有的继承关系去掉,让
SumA和SumB类都实现同一个接口Sum类,然后再重写Sum类中的reduce()方法。 - 让
SumA和SumB都继承一个比较通俗的基类(父类),将现有的继承关系去掉,采用依赖、聚合,组合等关系代替。
二、合成复用原则
尽量使用对象组合/聚合,而不是使用继承达到软件复用的目的。可以使系统更加的灵活,降低类与类之间的耦合度,一个类的变化对于其他类来说影响相对较少。
继承我们称之为白箱复用,相当于把实现的细节暴露给子类,组合/聚合 也成为黑箱复用,对类之外的对象是无法获取到实现细节的。
合成复用原则的核心是:复用时要尽量使用组合/聚合关系(关联关系),少用继承。
我们先来看一个数据库连接的例子:
// 数据库连接
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:里氏替换原则和合成复用原则详解的更多相关文章
- 设计模式 第一天 UML图,设计模式原则:开闭原则、依赖倒转原则、接口隔离原则、合成复用原则、迪米特法则,简单工厂模式
1 课程大纲 2 UML的概述 总结: UML unified model language 统一建模语言 一共有十种图: 类图 用例图 时序图 * 对象图 包图 组件图 部署图 协作图 状态图 (最 ...
- 想真正了解JAVA设计模式看着一篇就够了。 详解+代码实例
Java 设计模式 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结 设计模式分为 23 种经典的模式,根据用途我们又可以分为三大类.分别是创建型模式.结构型模式和行为型模式 列举几种设 ...
- 融会贯通——最常用的“合成复用原则”技能点Get
复用一个类的时候,多使用对象的组合/聚合的关联关系,而不是继承. 之前提到的"依赖倒转原则",是以里氏代换原则为基础的实现开闭原则目标的手段,这一条路线涉及到的是类的继承(包括单继 ...
- 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)
北风设计模式课程---里氏替换原则(Liskov Substitution Principle) 一.总结 一句话总结: 当衍生类能够完全替代它们的基类时:(Liskov Substitution P ...
- 学习java设计模式有用吗?懂这六个原则,编程更轻松
学习java设计模式有用吗?懂这六个原则,编程更轻松 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实 ...
- 设计模式课程 设计模式精讲 3-11 合成复用原则coding
1 课堂概念 1.0 继承关系的选择 1.1 起名 1.2 定义 1.3 组合聚合优缺点 1.4 继承优缺点 1.5 组合聚合区别 2 代码演练 2.1 反例 2.2 正例 3 疑问解答3.1 疑问解 ...
- 面象对象设计原则之七:合成复用原则(Composition/Aggregate Reuse Principle, CARP)
合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下: 合成复用原则(Composite Reuse Princi ...
- DesignPattern系列__07合成复用原则
基本介绍 合成复用原则的核心,就是尽量去使用组合.聚合等方式,而不是使用继承. 核心思想 1.找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起. 2.针对接口编程,而不是 ...
- 提高Java代码质量的Eclipse插件之Checkstyle的使用详解
提高Java代码质量的Eclipse插件之Checkstyle的使用详解 CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代 ...
随机推荐
- POJ 1679 判断最小树是否唯一
题意: 给你一个图,问你最小树是否唯一,唯一则输出最小数的权值,不唯一输出Not Unique! 思路: 题目问的是最小树是否唯一,其实也就是在问次小树是否等于最小树,如果等于则 ...
- hdu5040 不错的广搜
题意: 给你一个地图,让你从起点走到终点,然后图上有空地,墙,还有摄像头,摄像头有初始方向,每一秒摄像头都会顺时针旋转90度,每个摄像头有自己的观察范围,它所在的点,和当前它面向的那个点, ...
- MS08_067漏洞攻击
MS08_067 MS08_067是典型的缓冲区溢出漏洞,Windows的Server服务在处理特制RPC请求时存在缓冲区溢出漏洞,远程攻击者可以通过发送恶意的RPC请求触发这个溢出,导致完全入侵用户 ...
- Linux-鸟菜-5-目录配置-FHS
Linux-鸟菜-5-目录配置-FHS 这节内容比较休闲,主要就是介绍Linux的目录配置,也就是那些目录通常是干啥的,这个比较重要,需要我们去了解.但是我觉得通常看一遍记不住啥,也就记个大概,主要还 ...
- OGG-Oracle同步Sequence
一.需求,使用OGG同步软件,将Oracle 11g Sequence实时同步到19c新库中 参考文档 Implementing replication of cyclic sequences in ...
- 异常检测算法Robust Random Cut Forest(RRCF)关键定理引理证明
摘要:RRCF是亚马逊发表的一篇异常检测算法,是对周志华孤立森林的改进.但是相比孤立森林,具有更为扎实的理论基础.文章的理论论证相对较为晦涩,且没给出详细的证明过程.本文不对该算法进行详尽的描述,仅对 ...
- 算法学习笔记1语法 (C++组)持续更新
关于 #include <cstdio> 包含printf();和scanf(); c++中printf.scanf比cout.cin效率高很多 算法题里cin.cout可能超时,虽然可以 ...
- 使用sosreport命令生成诊断包
[RHEL]-7-常用系统状态检测命令 引言 这篇文章介绍RHEL中最常用的几个系统状态检测命令,包括:网络状态.内存.主机名及其架构.系统负载及其用户登录状态等. 文章目录 0×1.查看网络状态 0 ...
- python中类属性和数据属性的解释
python中的类叫class object,类的实例叫instance object. 类 Class Objects 类拥有两种操作,1.类属性 attribute references 2.实例 ...
- Linux_控制服务与守护进程
一.systemd 1.systemd简介 1️⃣:systemd是用户空间的第一个应用程序,即/sbin/init 2️⃣:init程序的类型: SysV风格:init(centos5),实现系统初 ...