Java设计模式之设计原则-里氏替换原则 (LSP)

👉文章示例代码👈

附链

你也可以在这些平台阅读本文:

定义

任何基类可以出现的地方,子类一定可以出现。

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

里氏替换原则通俗点来说就是:子类可以扩展父类的功能,但不能改变父类原有的功能

通过以上这句话,可以引申出以下几点含义:

  • **含义一:**子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • **含义二:**子类中可以增加自己特有的方法。
  • **含义三:**当子类的方法重载父类的方法时,方法的前置条件(即方法的输入、入参)要比父类方法的输入参数更宽松。
  • **含义四:**当子类的方法实现父类的方法时(重写、重载或者实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等。

如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系(可以采用依赖、聚集、组合等关系)。

含义讲解

在讲解开闭原则时,笔者通过扩展子类来覆写父类方法,同时子类提供额外方法来实现需求改动。👉点击跳转

代码粘贴至此:

/**
 * @author zhh
 * @description 食品打折类
 * @date 2020-02-05 00:27
 */
public class FoodDiscount extends Food {

    public FoodDiscount(Integer id, String name, Double price) {
        super(id, name, price);
    }

    public Double getOriginPrice() {
        return super.getPrice();
    }

    @Override
    public Double getPrice() {
        return this.getOriginPrice() * 0.6;
    }
}

含义一讲解

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

上段代码中 getPrice() 其实已经对于原来的 Food 类中的 getPrice() 含义发生了变化,对其进行了打折。但这并不符合里氏替换原则。所以这里我们将打折的方法放置到新增的方法中,调整后的代码如下:

/**
 * @author zhh
 * @description 食品打折类
 * @date 2020-02-05 00:27
 */
public class FoodDiscount extends Food {

    public FoodDiscount(Integer id, String name, Double price) {
        super(id, name, price);
    }

    public Double getDiscountPrice() {
        return super.getPrice() * 0.6;
    }
}

这样调整后,我们可以看到子类并没有覆盖父类的非抽象方法 getPrice()

含义二讲解

子类中可以增加自己特有的方法。
**
含义一讲解的代码片段中 getDiscountPrice() 是子类新增的特有方法,其父类中并不存在该方法。

含义三讲解

当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。

/**
 * @author zhh
 * @description 父类
 * @date 2020-02-07 16:50
 */
public class Father {

    public void method(HashMap hashMap) {
        System.out.println("父类被执行");
    }
}

/**
 * @author zhh
 * @description 子类
 * @date 2020-02-07 16:51
 */
public class Child extends Father {

    // 重载方法
    public void method(Map map) {
        System.out.println("子类Map被执行");
    }
}

/**
 * @author zhh
 * @description 测试类
 * @date 2020-02-07 16:53
 */
public class Test {

    public static void main(String[] args) {
        Child child = new Child();
        HashMap hashMap = new HashMap();
        child.method(hashMap);
    }
}

测试类的输出结果如下:

父类被执行

这个输出结构是正确的。

父类的方法入参是 HashMap 类型,而子类的方法入参是 Map 类型,子类的方法入参类型范围比父类大,那么子类的方法永远也不会被执行。

那我们反过来试下,调整代码如下。

public class Father {

    public void method(Map map) {
        System.out.println("父类被执行");
    }
}

public class Child extends Father {

    // 重载方法
    public void method(HashMap hashMap) {
        System.out.println("子类HashMap被执行");
    }
}

public class Test {

    public static void main(String[] args) {
        Child child = new Child();
        HashMap hashMap = new HashMap();
        child.method(hashMap);
    }
}

测试类的输出结果如下:

子类HashMap被执行

可以看到这个时候程序执行了子类的 method 方法,这样就违反了里氏替换原则,在实际的开发过程当中很容易引起业务逻辑的混乱。

含义四讲解

当子类的方法实现父类的方法时(重写、重载或者实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等。

/**
 * @author zhh
 * @description 父类
 * @date 2020-02-07 17:11
 */
public abstract class Father {

    public abstract Map method();
}

/**
 * @author zhh
 * @description 子类
 * @date 2020-02-08 11:13
 */
public class Child extends Father {

    @Override
    public HashMap method() {
        HashMap hashMap = new HashMap();
        System.out.println("子类method方法被执行");
        hashMap.put("username", "zhaohaihao");
        return hashMap;
    }
}

/**
 * @author zhh
 * @description 测试类
 * @date 2020-02-08 11:17
 */
public class Test {

    public static void main(String[] args) {
        Child child = new Child();
        System.out.println(child.method());
    }
}

测试类的输出结果如下:

子类method方法被执行
{username=zhaohaihao}

也就是说在实现父类的抽象方法时,子类的方法返回值范围一定要小于父类方法的返回值范围。

那我们反过来试下,我们将子类方法的返回值改为 Object ,而父类方法的返回值仍保持 Map 不变。

Object 是所有类的基类,因此它的范围是大于 Map 的,这个时候编辑器已经给出了错误提示,如上图。

继承的缺点

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法
  2. 降低了代码的灵活性
  3. 增强了耦合性。当父类的常量、变量或者方法被修改时,必需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大片的代码需要重构

优点

  1. 约束继承泛滥(开闭原则的一种体现)
  2. 加强程序健壮性,在程序变更的同时可以做到良好的兼容性
  3. 提高代码的重用性
  4. 提高代码的可扩展性

里氏替换原则反应了基类与子类之间的关系,同时也是对开闭原则的补充以及对实现抽象化的具体步骤的规范。

参考

展开阅读全文

Java软件设计模式精讲

05-28
内容简介: 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 本课程内容定位学习设计原则,学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构。本章将详细介绍开闭原则(OCP)、依赖倒置原则(DIP)、单一职责原则(SRP)、接口隔离原则(ISP)、迪米特法则(LoD)、里氏替换原则LSP)、合成复用原则(CARP)的具体内容。 为什么需要学习这门课程? 你在日常的开发中,会不会也遇到过同样的问题。系统出现问题,不知道问题究竟出在什么位置;当遇到产品需求,总是对代码缝缝补补,不能很快的去解决。而且平时工作中,总喜欢把代码堆在一起,出现问题时,不知道如何下手,工作效率很低,而且自己的能力也得不到提升。而这些都源于一个问题,那就是软件设计没做好。这门课能帮助你很好的认识设计模式,让你的能力得到提升。 课程大纲: 为了让大家快速系统了解设计模式知识全貌,我为您总结了思维导图,帮您梳理学习重点,建议收藏!
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值