开发原则三:里氏替换原则LSP

在这里插入图片描述

里氏替换原则(Liskov Substitution Principle)是面向对象设计的一个基本原则之一,它由《面向对象分析与设计》一书的作者Barbara Liskov教授在1987年提出。

里氏替换的两层含义

里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。

遵循里氏替换原则的好处:

提高代码的可复用性和可维护性。通过保持子类的兼容性和可替换性,我们可以更容易地使用和修改代码而不必担心破坏现有的功能。
促进多态性的应用。通过使用抽象和多态的方式组织代码,我们可以在运行时动态地选择适当的子类实现。
减少代码的耦合性。通过遵循里氏替换原则,我们可以降低代码之间的依赖性,使得系统更加灵活和可扩展。

如何遵循里氏替换原则:

子类必须完全实现父类的抽象方法,而不是修改父类已有的方法。
子类可以通过增加自己特有的方法来进行功能扩展,但不能重写或修改父类的非抽象方法。
子类在重写父类方法时,方法的前置条件(参数、返回类型等)应该与父类方法相同或更宽松。
后置条件即输出,假设我们的父类方法约定输出参数要大于0,调用父类方法的程序根据约定对输出参数进行了大于0的验证。而子类在实现的时候却输出了小于等于0的值。此时子类的涉及就违背了里氏替换原则

里氏替换的实现方法

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
根据上述理解,对里氏替换原则的定义可以总结如下:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
子类中可以增加自己特有的方法
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
通过重写父类的方法来完成新的功能写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。
企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。同样,由于“气球鱼”不会游泳,所以不能定义成“鱼”的子类;“玩具炮”炸不了敌人,所以不能定义成“炮”的子类等。

里氏替换实例

假设我们有一个 Rectangle(矩形)类和一个 Square(正方形)类,其中 Square 是 Rectangle 的子类。这两个类都有宽度和高度属性,并且都有计算面积的方法。
按照里氏替换原则,我们希望能够将 Square 对象视为 Rectangle 对象的替代品,并且在所有使用 Rectangle 类型的地方,也可以使用 Square 类型的对象。

public class Rectangle {
    private int width;
    private int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(height);
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(5);
        rectangle.setHeight(10);
        System.out.println("Rectangle area: " + rectangle.getArea()); // 输出:Rectangle area: 50

        Square square = new Square();
        square.setWidth(5);
        System.out.println("Square area: " + square.getArea()); // 输出:Square area: 25

        // 使用 Square 类型的对象替代 Rectangle 类型的对象
        Rectangle squareAsRectangle = new Square();
        squareAsRectangle.setWidth(5);
        squareAsRectangle.setHeight(10);
        System.out.println("Square as Rectangle area: " + squareAsRectangle.getArea()); // 输出:Square as Rectangle area: 50
    }
}

在上面的例子中,Square 类作为 Rectangle 类的子类,它重写了父类的设置宽度和高度方法,并保持宽度和高度相等。这样,在使用 Square 对象时,我们可以将其视为 Rectangle 对象的替代品,并正确计算出相应的面积。
通过里氏替换原则,我们能够在不影响程序正确性的前提下,将 Square 对象用于任何需要 Rectangle 对象的地方。这增强了代码的灵活性和可复用性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值