You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

里氏替换原则(LSP)中替换特性的实用价值探讨

里氏替换原则(LSP):为什么它是代码质量的关键

里氏替换原则说的直白点:子类必须能完全替代父类,而且替换后原有代码的逻辑不能出问题。很多人刚开始会觉得这是个“教条”,但实际上它是保障面向对象设计优势的核心规则——直接影响代码的复用性、灵活性和可维护性。

为什么要重视LSP?

  • 复用性拉满:父类的通用逻辑(比如上层的业务处理、工具方法)可以直接复用在所有符合LSP的子类上,不用为每个子类单独写重复代码。
  • 灵活性大增:你可以随时替换子类实现(比如把MySQL换成PostgreSQL,只要DAO层遵守父类契约),上层调用代码完全不用改,完美契合开闭原则。
  • 维护成本骤降:避免因为子类替换导致的隐性bug——很多奇怪的“偶现问题”,追根溯源就是违反了LSP,子类偷偷改了父类的行为契约。

违反LSP的反面例子:正方形vs长方形

这是经典的反例,很多人会想当然让Square继承Rectangle,但实际上这会破坏父类的行为契约。

首先定义父类Rectangle:

class Rectangle {
    protected int width;
    protected int height;

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

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

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

然后写Square子类,强行继承Rectangle,但正方形的宽高必须相等,所以重写set方法时会同时修改两个属性:

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 强行同步height
    }

    @Override
    public void setHeight(int height) {
        this.height = height;
        this.width = height; // 强行同步width
    }
}

现在写个调用方法,预期是把矩形改成宽10高20,面积应该是200:

public class Main {
    public static void resizeRectangle(Rectangle rect) {
        rect.setWidth(10);
        rect.setHeight(20);
        System.out.println("面积:" + rect.getArea());
    }

    public static void main(String[] args) {
        Rectangle normalRect = new Rectangle();
        resizeRectangle(normalRect); // 输出200,符合预期

        Square square = new Square();
        resizeRectangle(square); // 输出400,直接出错!
    }
}

问题出在哪?Square替换Rectangle后,违反了父类setWidth/setHeight的行为契约——父类的set方法是独立修改宽或高,子类却把两个属性绑死了,导致上层调用的逻辑完全失效,出现了意料之外的bug。

符合LSP的正确写法:抽象Shape父类

重构一下,把父类改成只定义所有形状都遵守的契约,不要包含子类做不到的行为:

abstract class Shape {
    // 所有形状都必须能计算面积,这是通用契约
    public abstract int getArea();
}

class Rectangle extends Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

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

    // 矩形自己的set方法,父类不定义这些子类专属的行为
    public void setWidth(int width) {
        this.width = width;
    }

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

class Square extends Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int getArea() {
        return side * side;
    }

    // 正方形自己的set方法,和矩形的方法互不干扰
    public void setSide(int side) {
        this.side = side;
    }
}

现在上层调用只依赖Shape的getArea方法,不管传Rectangle还是Square都能正常工作:

public class Main {
    public static void printArea(Shape shape) {
        System.out.println("面积:" + shape.getArea());
    }

    public static void main(String[] args) {
        Shape rect = new Rectangle(10, 20);
        printArea(rect); // 输出200,符合预期

        Shape square = new Square(20);
        printArea(square); // 输出400,符合正方形的计算逻辑,完全正常
    }
}

这时候LSP的优势就体现出来了:

  • 复用性:printArea方法可以复用在任何Shape子类上,以后加Circle、Triangle都不用改这个方法。
  • 灵活性:如果哪天需要把Rectangle换成Square,直接替换实例就行,上层代码完全不动。
  • 可维护性:每个子类的行为都符合父类的契约,不会出现隐性bug,调试和修改都省心。

总结

LSP不是凭空来的规则,它的本质是保障父类和子类的行为一致性——只有这样,继承才能真正成为代码复用的利器,而不是埋下bug的陷阱。遵守LSP的代码,上层逻辑只依赖抽象,不关心具体实现,自然能做到高内聚、低耦合,让代码更健壮、更易扩展。

内容的提问来源于stack exchange,提问作者ariko stephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:55:15