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


    个人主页金鳞踏雨

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

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

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

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

    一、原理概述

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

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

    案例分析

    案例一

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

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

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

    1. // 飞行行为接口
    2. public interface Flyable {
    3. void fly();
    4. }
    5. // 基类:鸟类
    6. public class Bird {
    7. }
    8. // 子类:能飞的鸟类
    9. public class FlyingBird extends Bird implements Flyable {
    10. @Override
    11. public void fly() {
    12. System.out.println("I can fly");
    13. }
    14. }
    15. // 子类:企鹅类,不实现Flyable接口!!!
    16. public class Penguin extends Bird {
    17. }

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

    案例二

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

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

    1. public abstract class Database {
    2. public abstract void connect();
    3. public abstract void disconnect();
    4. public abstract void executeQuery(String query);
    5. }

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

    1. public class MySQLDatabase extends Database {
    2. @Override
    3. public void connect() {
    4. // 实现MySQL的连接逻辑
    5. }
    6. @Override
    7. public void disconnect() {
    8. // 实现MySQL的断开连接逻辑
    9. }
    10. @Override
    11. public void executeQuery(String query) {
    12. // 实现MySQL的查询逻辑
    13. }
    14. // 其他针对MySQL的特定方法
    15. }
    16. public class PostgreSQLDatabase extends Database {
    17. // 类似地,为PostgreSQL实现相应的方法
    18. }
    19. public class SQLiteDatabase extends Database {
    20. // 类似地,为SQLite实现相应的方法
    21. }

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

    1. public class DatabaseClient {
    2. private Database database;
    3. public DatabaseClient(Database database) {
    4. this.database = database;
    5. }
    6. public void performDatabaseOperations() {
    7. database.connect();
    8. database.executeQuery("SELECT * FROM users");
    9. database.disconnect();
    10. }
    11. }
    12. public class Main {
    13. public static void main(String[] args) {
    14. // 使用MySQL数据库
    15. DatabaseClient client1 = new DatabaseClient(new MySQLDatabase());
    16. client1.performDatabaseOperations();
    17. // 切换到PostgreSQL数据库
    18. DatabaseClient client2 = new DatabaseClient(new PostgreSQLDatabase());
    19. client2.performDatabaseOperations();
    20. // 切换到SQLite数据库
    21. DatabaseClient client3 = new DatabaseClient(new SQLiteDatabase());
    22. client3.performDatabaseOperations();
    23. }
    24. }

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

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

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

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

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

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

    1. public class Bird {
    2. public void fly() {
    3. System.out.println("I can fly");
    4. }
    5. }
    6. public class Penguin extends Bird {
    7. @Override
    8. public void fly() {
    9. throw new UnsupportedOperationException("Penguins can't fly");
    10. }
    11. }

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

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

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

    1. // 栈
    2. public class Stack {
    3. private int top;
    4. private int[] elements;
    5. public Stack(int size) {
    6. elements = new int[size];
    7. top = -1;
    8. }
    9. public void push(int value) {
    10. if (top >= elements.length - 1) {
    11. throw new IllegalStateException("Stack is full");
    12. }
    13. elements[++top] = value;
    14. }
    15. public int pop() {
    16. if (top < 0) {
    17. throw new IllegalStateException("Stack is empty");
    18. }
    19. return elements[top--];
    20. }
    21. }
    22. // 正数的栈
    23. public class NonNegativeStack extends Stack {
    24. public NonNegativeStack(int size) {
    25. super(size);
    26. }
    27. @Override
    28. public void push(int value) {
    29. if (value < 0) {
    30. throw new IllegalArgumentException("Only non-negative values are allowed");
    31. }
    32. super.push(value);
    33. }
    34. }

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

    正确的写法应该是:

    1. public class NonNegativeStack extends Stack {
    2. public NonNegativeStack(int size) {
    3. super(size);
    4. }
    5. @Override
    6. public void push(int value) {
    7. super.push(value);
    8. }
    9. // 定义新的约束条件
    10. public void pushNonNegative(int value) {
    11. if (value < 0) {
    12. throw new IllegalArgumentException("Only non-negative values are allowed");
    13. }
    14. super.push(value);
    15. }
    16. }

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

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

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

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

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

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

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

  • 相关阅读:
    CMMI软件能力成熟度认证指南来了
    HTML期末学生大作业-宠物之家网页作业html+css+javascript
    python方法
    灌区流量监测设备:农田灌溉的“智慧眼”
    FIX - 克隆虚拟机NAT模式网络不通、不稳定、vMnet8网络故障、网卡冲突、ssh连接慢
    【应用笔记】Sub-1G系列产品CW32W031单片机CAD应用参考
    【回归预测】MATLAB 实现基于BP神经网络的多变量回归预测
    MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异
    了解WhatsAppBusiness这五个关键功能,助你提升客户体验
    转转集团宣布品牌焕新背后:黄炜继续模仿爱回收,公司多次被处罚
  • 原文地址:https://blog.csdn.net/weixin_43715214/article/details/134025515