LSP里氏替换原则
LSP强调子类必须能够替换父类。即子类应该具有与父类相同的行为和功能,而不仅仅是继承父类的属性和方法。LSP是对继承机制的约束规范、是指导接口与实现的设计原则。
LSP关键点
- 前置条件不能强化:子类方法的参数类型必须与父类相同或者更为宽松。
- 后置条件不能弱化:子类方法的返回值类型必须与父类相同或者是其子类型。
- 不变量要保持:子类不能破坏父类原有的约束条件。
- 异常类型需兼容:子类方法抛出的异常必须是父类方法抛出异常的子类型。
示例代码
不符合LSP的代码
class Shape { void draw() { System.out.println("Drawing a shape"); }
}
class Circle extends Shape {void draw() {System.out.println("Drawing a circle"); }
}
使用LSP优化后的代码
interface Drawable { void draw();
}
class Shape implements Drawable { public void draw() { System.out.println( "Drawing a shape"); }
}
class Circle extends Shape { public void draw() { System.out.println("Drawing a circle"); }
}
注意点
子类与父类具有一致的行为和功能:子类必须具有与父类相同的行为和功能,且不能改变父类的行为和功能。
避免继承滥用:避免通过继承来共享行为,继承是用来实现多态,而不是为了代码的重用。如果子类需要不同的功能和行为,那应该通过重写父类方法来实现。
多态与里氏替换原则
多态是实现里氏替换原则的技术手段
多态借助继承和方法重写,让子类对象能够替代父类对象使用。而里氏替换原则要求子类在替换父类时,不能改变程序原有的正确性。所以,多态为里氏替换原则提供了具体的实现方式。
举个例子,有一个抽象类 Shape定义了 area()方法,Rectangle和Circle作为它的子类分别对该方法进行了实现。在使用时,我们可以编写通用代码来处理Shape对象,这就是多态的体现,同时遵循了里氏替换原则。
多态是里氏替换原则的外在表现形式,它通过动态调用子类方法,实现了程序的灵活性和可扩展性。
里氏替换原则是对多态的约束和规范
多态的实现需要遵循一定规则,不然就可能引发问题。里氏替换原则明确了子类在行为上必须与父类保持兼容。要是违背了里氏替换规则,在运行时就可能出现意外情况。比如,子类重写方法时抛出了父类没有声明的异常,那么调用该方法的代码就可能无法正常处理。
里氏替换原则是多态的内在约束,它确保了子类对象能够无缝替换父类对象,从而为多态的实现提供了可靠的语义基础。
两者侧重点不同
- 多态:关注代码的实现机制,核心在于 “同一接口,多种实现”,目的是提高代码的灵活性。
- 里氏替换原则:重点在于设计层面,强调子类和父类之间的行为兼容性,目的是保证系统的稳定性。
共同服务于面向对象设计
里氏替换原则和多态都遵循开闭原则,也就是对扩展开放、对修改关闭。多态通过接口抽象来实现功能扩展,而里氏替换原则确保扩展不会破坏已有的系统。
参考
《架构整洁之道》-- Robert C.Mattin
《架构师的自我修炼》 – 李智慧
《设计模式之美》 – 王争