为何圆-椭圆问题的解决方案违反里氏替换原则(LSP)?
为什么圆-椭圆问题的解决方案会违反里氏替换原则?
咱们先把里氏替换原则(LSP)的核心掰扯清楚——子类必须能完全替换父类,而且替换后不能搞砸原有程序的正确性。说白了就是,只要父类能出现的场景,子类填进去就得能正常干活,不能整出意料之外的幺蛾子。
你提到的圆-椭圆问题,其实和经典的「矩形与正方形」案例本质完全一致,咱们拿这个矩形的例子拆解一下,你瞬间就能明白:
在数学里,正方形确实是矩形的一种——四条边都相等的特殊矩形。这种“是一种”的关系很容易让人下意识想用继承来建模,比如让Square类继承Rectangle类。但问题出在面向对象的继承要求子类必须遵守父类的所有行为契约,而这俩货的契约根本不兼容:
Rectangle有width和height两个独立属性,对应的setWidth()和setHeight()方法是完全独立的——改宽不会影响高,改高也不会影响宽,这是Rectangle的核心行为契约。- 但
Square的宽和高必须始终相等,这是它的类不变量。如果让Square继承Rectangle,它就得继承那两个独立的setter方法,这时候矛盾就爆发了:- 要是有人调用
square.setWidth(5),按照正方形的规则,height也得跟着改成5,但Rectangle的setWidth可没这逻辑; - 再比如有一段给
Rectangle写的代码:
你把void resizeRectangle(Rectangle rect) { rect.setWidth(rect.getWidth() * 2); rect.setHeight(rect.getHeight() * 3); }Square传进去执行完,这个“正方形”的宽变成了原来的2倍,高变成了3倍——直接就不是正方形了!这既破坏了Square的类不变量,也违背了调用者对Rectangle行为的预期,完全搞砸了程序逻辑。
- 要是有人调用
这就实打实违反了LSP:子类(Square/Circle)替换父类(Rectangle/Ellipse)后,程序的行为不符合原有逻辑,出现了错误。
再回到圆-椭圆问题上:椭圆有长半轴和短半轴两个可独立修改的属性,而圆要求这两个半轴必须始终相等。如果用继承让Circle继承Ellipse,同样会遇到一模一样的问题——当一段预期处理Ellipse的代码修改了其中一个半轴时,传入的Circle就不再是圆了,直接崩掉原有逻辑。
本质上,问题的根源是:数学上的“是一种”关系,和面向对象中适合用继承的“是一种”关系根本不是一回事。面向对象的继承要求子类完全遵守父类的所有契约,但圆/正方形的约束比椭圆/矩形更严格,导致子类无法满足父类的行为预期,最终违反了里氏替换原则。
内容的提问来源于stack exchange,提问作者urig
相关产品推荐
相关产品推荐

