【问题标题】:SOLID Design Principles : Liskov Substitution Principle and Dependency Inversion PrincipleSOLID设计原则:Liskov替换原则和依赖倒置原则
【发布时间】:2022-03-31 22:38:13
【问题描述】:

只是向 Stack Overflow 和 Microsoft 开发社区提出的关于称为 SOLID 的 OO 软件设计原则的想法和问题。请问里氏替换原则和依赖倒置原则有什么区别?我已经考虑了一段时间,但我不确定有什么区别。请问你能告诉我吗?非常欢迎任何想法/反馈。

【问题讨论】:

标签: solid-principles liskov-substitution-principle dependency-inversion


【解决方案1】:

Liskov 的替代原则陈述如下:

一个类应该可以直接替换为它的基类

这意味着如果一个子类扩展了一个父类,它应该是直接可替换的。 Image viewable here

如果你以 Bird 为例。不是所有的鸟都会飞 - 但有些会飞。

让我们看一个java示例:

Bird  ostrich = new Ostrich();

如果我要将我的鸵鸟视为鸟(是基类/父类),并且我们在 Bird 中具有让鸵鸟飞翔的功能,即使它们不应该!

所以我们可以分离出结构: Refactored hierarchy for Liskov's

依赖倒置当被认为是一个单独的原则时更容易。

如果我们有一个类调用一个类。这使得以后更改它变得非常困难,并且需要我们更改源代码。让我们再看一个代码示例。

public class App {
    public static void main(String[] args) {
        Greeting greeting = new Greeting();
        greeting.sayHello(new Friend()); 
        greeting.sayHello(new Enemy());
    }
}

public class Greeting {
    public void sayHello(Person person) {
         person.greet();
    }
}

public interface Person {
    public void greet();
}

public class Friend implements Person {
    public void greet() {
        System.out.println("Hello my friend");
    }
}

public class Enemy implements Person {
    public void greet() {
        System.out.println("Go away");
    }
}

这里我们传递了两个项目(朋友和敌人)的父对象(人)。如果我们通过一个 Friend 对象,我们将需要一个单独的相同方法用于 Enemy 方法。我们可以使用 Open/Closed 原则来拥有一个可以调用 Friend 或 Enemy 或将来可能扩展 Person 的任何方法的方法。依赖倒置是传递父对象而不是 sayHello() 方法创建一个类。这意味着我们调用哪个Parent对象取决于App,而不是sayHello()决定调用哪个对象。

最好使用依赖倒置。类 greet() 调用不是一成不变的,只要传递的类是实现 Person 的类。

这意味着 App 不再依赖 Friend。 Friend 或 Enemy 是否被调用取决于 App。

像这样传递责任意味着我们的代码可以很容易地维护和更改。使用上下文和依赖注入,配置文件可以确定在引用指定接口时我们要使用哪种类型的对象。

【讨论】:

  • 您好 Christoph,非常感谢您的深入回答。真了不起 !作为回应,我只想说,在您的示例类中 Bird 可能将 fly 函数实现为具有 Ostrich 的空实现的虚函数?然后我们正在处理 Bird 的抽象,我认为这与您处理 Person 抽象的 App 示例的概念相似?非常感谢和最好的问候,塔赫哈桑
  • 您好 Taher,您 100% 正确,我们可以使用 fly 方法在鸵鸟中实现“空逻辑”。但是大规模地想象一下 - 向后和向前传递空功能并拥有不做任何事情的功能。它可以编译,但我不认为它是最佳实践——实际上所有 SOLID 原则都是最佳实践和可维护性的指南。
【解决方案2】:

在某种意义上,LSP 和 DIP 是对立的。

  • LSP 管理继承层次结构中类之间的关系
    (即父类和子类)。
  • DIP 管理继承层次结构之外的类之间的关系
    (即非父类和子类)。

对于视觉,考虑两个父类和两个子类之间的六种潜在关系。

  • LSP 关注关系 1 和 2。
    • 只有当 Child1Child2 是子类型而不仅仅是子类时,1 和 2 才可以。
  • DIP 关注关系 3、4、5 和 6。
    • 3 没问题。
    • 6 被禁止。
    • 4 和 5 可以向上,但不能向下。

【讨论】:

    【解决方案3】:

    替换原则意味着您使用抽象。抽象是好的,因为它们使您的代码更加通用、可重用和有价值。例如,如果您的代码使用抽象的Vehicle 而不是更具体的Car,那么您也可以使用相同的代码而无需对其他车辆进行任何更改,例如BicycleTruck 甚至@987654325 @ 它将继续工作而不会出现问题。当然,要使这种“替代”起作用,您必须确保所有Vehicles 遵循相同的约定,这通常意味着它们实现了一个公共接口或扩展了一个公共超类。

    依赖注入让你的代码更好,因为它减少了类之间不必要的联系。这再次使这些类在不同的场景中可重用。例如,如果您的班级从文件中获取数据,那么下次您的数据将在数据库中时,您将遇到问题。你的班级会突然变成一个 if-then-else 案例的沼泽,试图处理不同的事情,这将是一场噩梦。依赖注入将你的类从这个责任中解放出来——即你的类只是“需要数据”,调用者有责任确保数据已连接(也称为“注入”)到其中。从技术上讲,依赖注入只是简单地设置一个变量,例如myObject.dataSource = databaseDataSource,或myObject.dataSource = fileDataSource

    【讨论】:

    • 非常感谢您的 cmets Jurez。我完全理解了 Liskov 替换原则,现在我想我理解了依赖倒置原则的驱动力。再次感谢您的视觉表现和示例。这些原则使系统能够在具有底层继承层次结构和多态性的分层结构中尽可能地处理松散耦合抽象的系统结构。非常有趣的讨论!非常感谢和最好的问候,塔赫
    猜你喜欢
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-05
    • 2016-08-20
    • 1970-01-01
    • 2017-09-09
    相关资源
    最近更新 更多