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

软件设计原则之里氏替换原则

定义

里氏替换原则(Liskov Substitution Principle, LSP)是 SOLID 原则之一,它指出:子类型必须能够替换它们的基类型。换句话说,在使用基类的地方应当可以透明地使用派生类的对象而不会影响程序的正确性。

违反里氏替换原则的一个典型反例是正方形(Square)和矩形(Rectangle)的关系。在几何学中,正方形是一种特殊的矩形,其中所有边等长。然而,如果我们在面向对象编程中直接继承关系来实现这一点,可能会违反 LSP。

反例

class Rectangle {
    protected int width = 0;
    protected int height = 0;

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

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

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

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width); // 违背了 LSP,因为正方形的宽高需要同步设置
    }

    @Override
    public void setHeight(int height) {
        super.setWidth(height); // 同样违背了 LSP
        super.setHeight(height);
    }
}
问题

如果我们试图使用 Rectangle 类型的引用指向 Square 实例,并尝试改变其宽度或高度,我们会发现行为不符合预期:

public static void main(String[] args) {
    Rectangle rect = new Square(); // 正常情况下期望能这样做
    rect.setWidth(5);
    rect.setHeight(4);

    System.out.println(rect.getArea()); // 如果 Square 继承自 Rectangle,这可能输出 25 而不是预期的 20
}

在这个例子中,Square 类的行为与 Rectangle 类不一致,导致了违反 LSP 的情况发生。这是因为 Square 强制要求宽度和高度保持一致,而 Rectangle 则允许它们独立变化。

如何修正

为了解决这个问题,我们需要重新考虑设计。一种方法是避免让 Square 直接继承自 Rectangle,而是将它们视为两个独立但相关的类,或者通过组合而非继承来表达它们之间的关系。例如,可以通过定义接口来描述两者共有的行为,而不依赖于具体的实现细节。

正确的做法可能是确保任何使用 Rectangle 的地方都能够安全地接受 Rectangle 的任何子类,而不改变程序的正确性和预期行为。这样就遵循了里氏替换原则。

修正

设计接口
// 矩形接口 求面积 周长等
public interface IRectangle {
    int area();
}
面向对象 设计1 使用组合 分离Square
 矩形类
@AllArgsConstructor
@Data
public class Rectangle1 implements IRectangle {
    protected int width = 0;
    protected int height = 0;

    @Override
    public int area() {
        return width * height;
    }
}
正方形类
public class Square1 implements IRectangle {

    private int edge;

    public Square1(Rectangle rectangle) {
        if (rectangle.width != rectangle.height) {
            throw new RuntimeException("不是正方形,长方形必须长宽相等");
        }
        this.edge = rectangle.getWidth();
    }

    @Override
    public int area() {
        return edge * edge;
    }
}
面向对象 设计2 使用继承 添加特殊子类标记完善矩形类

问题;这里扩充父类,会违反单一职责原则吗?

如果一个对象承担了太多的职责,至少存在以下两个缺点:

1、一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力; 

2、当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。

我认为是没有违反单一职责原则的。比如再有一个长方形类,长方形会继承矩形判断是否是正方形的属性和方法,当长方形当做矩形(多态、依赖倒置)使用时是可以有这个判断条件的,因为矩形本身就含有是否是正方形这个功能。

矩形类
@Data
public class Rectangle implements IRectangle {
    protected int width = 0;
    protected int height = 0;

    protected boolean isSquare;

    public Rectangle() {
        this(false);
    }

    public Rectangle(boolean isSquare) {
        this.isSquare = isSquare;
    }

    public void setHeight(int height) {
        if (isSquare) {
            this.width = height;
            this.height = height;
        } else {
            this.height = height;
        }
    }

    public void setWidth(int width) {
        if (isSquare) {
            this.width = width;
            this.height = width;
        } else {
            this.width = width;
        }
    }

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

    public boolean isSquare() {
        return isSquare;
    }
}
正方形类
public class Square extends Rectangle {

    public Square(Rectangle rectangle) {
        if (rectangle.width != rectangle.height) {
            throw new RuntimeException("不是正方形,必须长宽相等");
        }
    }

    public Square(boolean isSquare) {
        super(isSquare);
    }
}

测试

public class Client {
    public static void main(String[] args) {
        // 使用组合
        Rectangle1 rectangle1 = new Rectangle1(5, 4);
        Square1 square = new Square1(rectangle1);
        System.out.println(square.area());

        // 使用继承 添加特殊子类的标记
        Rectangle rectangle = new Square(true);
        rectangle.setHeight(5);
        rectangle.setWidth(4);
        System.out.println(rectangle.area());
    }
}

总结

里氏替换原则(Liskov Substitution Principle,LSP)由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士 在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy) 里提出来的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。

关于里氏替换原则的例子,最有名的是“正方形不是长方形”。当然,生活中也有很多类似的例子,例如,企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。同样,由于“气球鱼”不会游泳,所以不能定义成“鱼”的子类;“玩具炮”炸不了敌人,所以不能定义成“炮”的子类等。

相关文章:

  • 基于 EMA12 指标结合 iTick 外汇报价 API 、股票报价API、指数报价API的量化策略编写与回测
  • HCIE-SLAAC
  • 字节跳动实习生主导开发强化学习算法,助力大语言模型性能突破
  • linux下配置allure的环境变量使之变为可执行文件
  • 【LLM大模型】LangChain学习
  • 多条件排序(C# and Lua)
  • 生成树(STP)协议
  • 基于 Java 和深度学习的图像分类应用实践
  • 大屏设计新纪元:定制视觉盛宴
  • 【WRF模拟】WPS预处理设置生成文件地址
  • XSS-labs(反射型XSS) 靶场 1-13关 通关
  • 图解AUTOSAR_CP_E2E_Library
  • Linux系统——keepalived安装与部署
  • 用 pytorch 从零开始创建大语言模型(一):理解大型语言模型
  • 关于 Redis 缓存一致
  • 定积分与不定积分在概率统计中的应用
  • idea问题(三)pom文件显示删除线
  • C++ - 从零实现Json-Rpc框架-2(服务端模块 客户端模块 框架设计)
  • 从报错到成功:Mermaid 流程图语法避坑指南✨
  • C# HTTP 文件上传、下载服务器
  • 李在明涉嫌违反《公职选举法》案将于5月1日宣判
  • 青海省林业和草原局副局长旦增主动投案,正接受审查调查
  • 国家统计局:一季度全国规模以上文化及相关产业企业营业收入增长6.2%
  • 法治日报调查直播间“杀熟”乱象:熟客越买越贵,举证难维权不易
  • 全国电影工作会:聚焦扩大电影国际交流合作,提升全球影响力
  • 当智驾成标配,车企暗战升级|2025上海车展