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

【23种设计模式】里氏替换原则

个人主页:金鳞踏雨

个人简介:大家好,我是金鳞,一个初出茅庐的Java小白

目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作

我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~

本文来自抖音《IT楠老师》设计模式课程,下面是本人结合原课件的一些学习心得。

一、原理概述

子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变正确性不被破坏

举一个最简单的例子,就是我们定义了一个函数,它的参数是 XXX(List list),当我们传入ArrayList的对象(它的子类),不会有任何问题!

案例分析

案例一

// 基类:鸟类
public class Bird {public void fly() {System.out.println("I can fly");}
}// 子类:企鹅类
public class Penguin extends Bird {// 企鹅不能飞,所以覆盖了基类的fly方法,但这违反了里氏替换原则public void fly() {throw new UnsupportedOperationException("Penguins can't fly");}
}

企鹅不会飞,所以在企鹅类中重写的fly(),会抛出异常。那么这个子类并不能很好的替代父类,因为它执行fly() 方法会报错!!!

为了遵循LSP,我们可以重新设计类结构,将能飞的行为抽象到一个接口中,让需要飞行能力的鸟类实现这个接口,对于企鹅,就不实现这个方法

// 飞行行为接口
public interface Flyable {void fly();
}// 基类:鸟类
public class Bird {
}// 子类:能飞的鸟类
public class FlyingBird extends Bird implements Flyable {@Overridepublic void fly() {System.out.println("I can fly");}
}// 子类:企鹅类,不实现Flyable接口!!!
public class Penguin extends Bird {
}

通过这样的设计,我们遵循了里氏替换原则,同时也保证了代码的可维护性和复用性。

案例二

假设我们正在开发一个支持多种数据库的程序,包括MySQL、PostgreSQL和SQLite。我们可以使用里氏替换原则来设计合适的类结构,确保代码的可维护性和扩展性。

首先,我们定义一个抽象的Database基类,它包含一些通用的数据库操作方法,如connect()、disconnect()和executeQuery()。这些方法的具体实现将在子类中完成。

public abstract class Database {public abstract void connect();public abstract void disconnect();public abstract void executeQuery(String query);
}

然后,为每种数据库类型创建一个子类,继承自Database基类。这些子类需要实现基类中定义的抽象方法,并可以添加特定于各自数据库的方法。

public class MySQLDatabase extends Database {@Overridepublic void connect() {// 实现MySQL的连接逻辑}@Overridepublic void disconnect() {// 实现MySQL的断开连接逻辑}@Overridepublic void executeQuery(String query) {// 实现MySQL的查询逻辑}// 其他针对MySQL的特定方法
}public class PostgreSQLDatabase extends Database {// 类似地,为PostgreSQL实现相应的方法
}public class SQLiteDatabase extends Database {// 类似地,为SQLite实现相应的方法
}

这样设计的好处是,我们可以在不同的数据库类型之间灵活切换,而不需要修改大量代码。只要这些子类遵循里氏替换原则,我们就可以放心地使用基类的引用来操作不同类型的数据库。例如:

public class DatabaseClient {private Database database;public DatabaseClient(Database database) {this.database = database;}public void performDatabaseOperations() {database.connect();database.executeQuery("SELECT * FROM users");database.disconnect();}
}public class Main {public static void main(String[] args) {// 使用MySQL数据库DatabaseClient client1 = new DatabaseClient(new MySQLDatabase());client1.performDatabaseOperations();// 切换到PostgreSQL数据库DatabaseClient client2 = new DatabaseClient(new PostgreSQLDatabase());client2.performDatabaseOperations();// 切换到SQLite数据库DatabaseClient client3 = new DatabaseClient(new SQLiteDatabase());client3.performDatabaseOperations();}
}

通过遵循里氏替换原则,我们确保了代码的可维护性和扩展性。如果需要支持新的数据库类型,只需创建一个新的子类,实现Database基类中定义的抽象方法即可。

好了,我们稍微总结一下。虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。

  • 多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。
  • 里式替换是一种设计原则,是用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。

二、哪些代码明显违背了 LSP?

1. 子类覆盖或修改了基类的方法

当子类覆盖或修改基类的方法时,可能导致子类无法替换基类的实例而不引起问题。这违反了LSP,会导致代码变得脆弱和不易维护。

public class Bird {public void fly() {System.out.println("I can fly");}
}public class Penguin extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("Penguins can't fly");}
}

在这个例子中,Penguin类覆盖了Bird类的fly()方法,抛出了一个异常。这违反了LSP,因为现在Penguin实例无法替换Bird实例

2. 子类违反了基类的约束条件

当子类违反了基类中定义的约束条件(如输入、输出或异常等),也会违反LSP。

// 栈
public class Stack {private int top;private int[] elements;public Stack(int size) {elements = new int[size];top = -1;}public void push(int value) {if (top >= elements.length - 1) {throw new IllegalStateException("Stack is full");}elements[++top] = value;}public int pop() {if (top < 0) {throw new IllegalStateException("Stack is empty");}return elements[top--];}
}// 正数的栈
public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}@Overridepublic void push(int value) {if (value < 0) {throw new IllegalArgumentException("Only non-negative values are allowed");}super.push(value);}
}

在这个例子中,NonNegativeStack子类违反了Stack基类的约束条件,因为它在push()方法添加了一个新的约束,即只允许非负数入栈。这使得NonNegativeStack实例无法替换Stack实例而不引发问题,违反了LSP。

正确的写法应该是:

public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}@Overridepublic void push(int value) {super.push(value);}// 定义新的约束条件public void pushNonNegative(int value) {if (value < 0) {throw new IllegalArgumentException("Only non-negative values are allowed");}super.push(value);}
}

3. 子类与基类之间缺乏"is-a"关系

子类基类之间缺乏真正的"is-a"关系时,也可能导致违反LSP。例如,如果一个类继承自另一个类,仅仅因为它们具有部分相似性,而不是完全的"is-a"关系,那么这种继承关系可能不满足LSP。

为了避免违反LSP,我们需要在设计和实现过程中注意以下几点:

  • 确保子类和基类之间存在真正的"is-a"关系。(你就是一个它)
  • 遵循其他设计原则,如单一职责原则、开闭原则和依赖倒置原则。

文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~

希望能和大佬们一起努力,诸君顶峰相见

再次感谢各位小伙伴儿们的支持!!!

http://www.lryc.cn/news/206275.html

相关文章:

  • 嵌入式系统设计师考试笔记之操作系统基础复习笔记一
  • Unity开发之观察者模式(事件中心)
  • 16、window11+visual studio 2022+cuda+ffmpeg进行拉流和解码(RTX3050)
  • 【C++笔记】如何用检查TCP或UDP端口是否被占用
  • “华为杯”研究生数学建模竞赛2015年-【华为杯】D题:面向节能的单/多列车优化决策问题
  • 『第三章』雨燕栖息地:Swift 开发环境
  • elasticsearch-5.6.15集群部署,如何部署x-pack并添加安全认证
  • C++ list 模拟实现
  • Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (三)
  • 主流电商平台价格如何高频监测
  • Spring关于注解的使用
  • 图像处理入门 1(Introduction to image processing)
  • 中国大模型开源创新与合作的新篇章 | 2023 CCF中国开源大会
  • vue项目切换页面白屏的解决方案
  • 5G vs 4G
  • 【JavaEE重点知识归纳】第11节:认识异常
  • 一键自助建站系统api版系统源码
  • 全国三维数字化创新设计大赛湖北赛区省赛成功举办
  • OpenCV #以图搜图:均值哈希算法(Average Hash Algorithm)原理与实验
  • 博通BCM575系列RDMA网卡驱动bnxt_re分析(一)
  • 集合总结-
  • 【知识串联】概率论中的值和量(随机变量/数字特征/参数估计)【考研向】【按概率论学习章节总结】
  • 上游服务不可用了,下游服务如何应对?
  • WebGL笔记:矩阵的变换之平移的实现
  • XTU-OJ 1187-Candy
  • 基于 nodejs+vue城市轨道交通线路查询系统mysql
  • 电商时代,VR全景如何解决实体店难做没流量?
  • 操作系统-浅谈CPU与内存
  • K8s 部署 CNI 网络组件+k8s 多master集群部署+负载均衡
  • 若依微服务上传图片文件代理配置