LSP里氏替换原则 Barbara Liskov 将 LSP 定义如下
Posted: Sun Jan 26, 2025 6:47 am
“如果对于每个 S 类型的对象 o1 都存在一个 T 类型的对象 o2,这样对于 T 中定义的所有程序 P,当 o1 替换 o2 时 P 的行为保持不变,那么 S 是 T 的子类型。”
原则上,这意味着在继承时,子类必须始终包含超类的所有属性,以便超类可以使用它们。子类可能不包含对超类功能的任何更改。然而,上层阶级可以通过新的功能进行扩展。
让我们再看看我们的汽车示例。我们有豪华车。这提供了某些功能——例如加速和制动。现在我们有两个子类,即跑车和小型车。两个子类必须能够随时使用超类的方法。但是,子类可能具有扩展属性。例如,跑车还可以包含“激活运动模式”功能,该功能可以控制车辆的驾驶特性。
因此,LSP 比 OCP 更进了一步,为通过超类向子类多重继承设置了条件。
ISP接口隔离原则
ISP 的目的不是强迫用户实现不需要的部分接口。这应该确保接口不会变得太大,也不会缩小到特定用途。
这在图形表示中很容易看出。下图显示超类(class)实现了几个操作(Op1,Op2,Op3)。然而,User1仅使用Op1和User2,而User3仅使用Op2和Op3。在这种情况下,即使未调用该操作,User1 也会依赖 Op2 和 Op3。例如,如果你要改变上层Op2的实现,那么User1也必须进行全新的编译和部署。尽管 User1 使用的模块实际上没有变化。
该问题的解决方案是将操作拆分为接口,如下图所示。在静态类型语言(例如 Java)中,User1 的源代码将仅依赖于类 User1Op1 和关联的 Op1,而不再依赖于该类。
ISP 旨在防止携带不必要负载的模块依赖性导致完全意外的问题。依赖 比利时消费者电子邮件列表 性缓解可确保代码更改不会导致复杂且广泛的更改或问题。附加层带来的额外工作随后确保了架构可以更好地处理修改。
DIP依赖倒置原理
五个 SOLID 原则中的最后一个是依赖倒置原则 (DIP)。 DIP 旨在明确表示,源代码依赖性仅与抽象而不是具体相关的系统是最灵活的。
在 Java 中,这意味着在使用 use、import 和 include 语句时只能引用源模块 - 例如接口、抽象类或确保任何其他形式的抽象的模块。这是为了确保不存在对特定模块的依赖性。
然而,使用这一原则作为规则是不现实的,因为软件系统也依赖于具体的实体。例如,在 Java 中,String 类的设计非常特殊。试图使它们抽象化没有多大意义。这里也不应该避免对字符串对象的依赖。
基于这个论点,DIP应该主要指那些正在开发并且显然可以修改的软件部分。
由此可以得出什么结论呢?
总之,每个原则都可以对良好软件架构的开发产生重大影响。必须正确解释这些原则并在软件上下文中使用这些原则。以在不修改软件的情况下扩展软件?当马丁在 90 年代接手迈耶的原理时,他在技术上以不同的方式实现了它。对于 Meyer 来说,解决方案是使用面向对象世界中已知的继承。在当时,这是软件可维护性和可扩展性的重要因素。一个例子:假设我们有两个类:汽车和跑车。跑车类别将继承乘用车类别的所有重要属性和功能。然后将在跑车类别中添加特定的功能和特性。这里的依赖只朝一个方向发展,这是一件好事。
我不会将这些原则描述为良好软件架构的基石,而是将其视为每个软件开发人员和架构师都应该内化的良好基础。
可以看出,这些原则部分地相互递归地联系或相互构建。 SOLID 原则在更高的架构主题中一次又一次出现,因此对于所有有抱负的软件开发人员和架构师来说都是重要的专业知识。
您想了解更多关于 adesso 世界中令人兴奋的话题吗?那么请看一下我们之前发布的博客文章。
原则上,这意味着在继承时,子类必须始终包含超类的所有属性,以便超类可以使用它们。子类可能不包含对超类功能的任何更改。然而,上层阶级可以通过新的功能进行扩展。
让我们再看看我们的汽车示例。我们有豪华车。这提供了某些功能——例如加速和制动。现在我们有两个子类,即跑车和小型车。两个子类必须能够随时使用超类的方法。但是,子类可能具有扩展属性。例如,跑车还可以包含“激活运动模式”功能,该功能可以控制车辆的驾驶特性。
因此,LSP 比 OCP 更进了一步,为通过超类向子类多重继承设置了条件。
ISP接口隔离原则
ISP 的目的不是强迫用户实现不需要的部分接口。这应该确保接口不会变得太大,也不会缩小到特定用途。
这在图形表示中很容易看出。下图显示超类(class)实现了几个操作(Op1,Op2,Op3)。然而,User1仅使用Op1和User2,而User3仅使用Op2和Op3。在这种情况下,即使未调用该操作,User1 也会依赖 Op2 和 Op3。例如,如果你要改变上层Op2的实现,那么User1也必须进行全新的编译和部署。尽管 User1 使用的模块实际上没有变化。
该问题的解决方案是将操作拆分为接口,如下图所示。在静态类型语言(例如 Java)中,User1 的源代码将仅依赖于类 User1Op1 和关联的 Op1,而不再依赖于该类。
ISP 旨在防止携带不必要负载的模块依赖性导致完全意外的问题。依赖 比利时消费者电子邮件列表 性缓解可确保代码更改不会导致复杂且广泛的更改或问题。附加层带来的额外工作随后确保了架构可以更好地处理修改。
DIP依赖倒置原理
五个 SOLID 原则中的最后一个是依赖倒置原则 (DIP)。 DIP 旨在明确表示,源代码依赖性仅与抽象而不是具体相关的系统是最灵活的。
在 Java 中,这意味着在使用 use、import 和 include 语句时只能引用源模块 - 例如接口、抽象类或确保任何其他形式的抽象的模块。这是为了确保不存在对特定模块的依赖性。
然而,使用这一原则作为规则是不现实的,因为软件系统也依赖于具体的实体。例如,在 Java 中,String 类的设计非常特殊。试图使它们抽象化没有多大意义。这里也不应该避免对字符串对象的依赖。
基于这个论点,DIP应该主要指那些正在开发并且显然可以修改的软件部分。
由此可以得出什么结论呢?
总之,每个原则都可以对良好软件架构的开发产生重大影响。必须正确解释这些原则并在软件上下文中使用这些原则。以在不修改软件的情况下扩展软件?当马丁在 90 年代接手迈耶的原理时,他在技术上以不同的方式实现了它。对于 Meyer 来说,解决方案是使用面向对象世界中已知的继承。在当时,这是软件可维护性和可扩展性的重要因素。一个例子:假设我们有两个类:汽车和跑车。跑车类别将继承乘用车类别的所有重要属性和功能。然后将在跑车类别中添加特定的功能和特性。这里的依赖只朝一个方向发展,这是一件好事。
我不会将这些原则描述为良好软件架构的基石,而是将其视为每个软件开发人员和架构师都应该内化的良好基础。
可以看出,这些原则部分地相互递归地联系或相互构建。 SOLID 原则在更高的架构主题中一次又一次出现,因此对于所有有抱负的软件开发人员和架构师来说都是重要的专业知识。
您想了解更多关于 adesso 世界中令人兴奋的话题吗?那么请看一下我们之前发布的博客文章。