依赖倒置原则和组合

4
我正在学习SOLID原则,现在停留在“依赖倒置原则”上。这意味着对象应该已经被实例化并传递给另一个对象,也就是说“组合”不能与“依赖倒置原则”一起使用,我理解的对吗?还是我漏掉了什么?更新:假设您有一个类,这个类有一个属性,它是指向另一个对象的引用,我们有两种解决方案(对我来说):
  1. 在类外创建对象并将其传递给类。(依赖)
  2. 在类内部创建对象(组合)。
谢谢。

我在这里没有看到任何矛盾。一个对象由一些其他对象组成,这些对象在构造函数中传递给它,那又怎样? - Frank Puffer
我猜你混淆了类之间的依赖和对象之间的依赖。当然,一个组合对象依赖于它的组件。但是,如果你使用抽象(接口),这并不意味着类之间存在依赖关系。 - Frank Puffer
@FrankPuffer 我可以使用依赖注入来进行组合吗? - user6514731
2个回答

13

你的困惑来自于对组合的理解。被另一个对象拥有的对象取决于拥有对象的生命周期。这并不意味着你必须在拥有类内创建拥有对象。

如果你在一个类中创建对象,这个类就与创建的类耦合紧密。你无法在不改变创建其他类的类的情况下交换实现。

例如: enter image description here

上面的图片中,你有一个使用类Server的类Client。假设这是一个组合,Client有一个类型为Server的属性。

如果你在客户端类内创建Server类的一个实例,它可能看起来像这样:

public class Client {
    private Server server;

    public Client(){
        this.server = new Server();
    }
}

现在假设你想交换服务器的实现。你需要更改Client类的实现,因为唯一的交换方式是创建另一个类(也许称为AnotherServer)的实例。

public class Client {
    private AnotherServer anotherServer;

    public Client(){
        this.anotherServer = new AnotherServer();
    }
}

这表明Client类与Server类高度耦合。

为了方便地更改Server的实现并修改Client的行为,最好将Client组合成抽象(抽象类或接口)。这样做意味着您不能在拥有类中创建所需对象,因为您只能创建具体类。创建类意味着调用构造函数并依赖于已创建的类。

实现组合(-Client由Server组成-)的更好方法是通过setter方法或构造函数注入它。这样可以将实现类隐藏在接口后面。

例如: enter image description here

在第二张图片中,我们保护Client不知道Server的具体实现。它只依赖于服务器接口。这种依赖关系并不那么严重,因为Client定义了接口。他决定了Server接口的必要功能。为了表明服务器接口属于Client,它称为“ClientServer”。

为了组合您的Client,您需要在类外部创建ClientServer接口的具体类,并通过构造函数或setter方法注入它。

...
FirstServer first = new FirstServer();
Client client = new Client(first);

client.setServer(new SecondServer());
...

像这样,您可以轻松地在客户端中交换使用的服务器实现,甚至在运行时。

这种机制称为依赖反转原则(DIP)。但是为什么呢?客户端类仍然依赖于服务器接口。如果接口发生更改,客户端也必须进行更改。是的,这是正确的。但是客户端决定它在该接口中需要哪些功能。因此,通常情况下,当客户端表示需要更改时,接口才会发生更改。接口之所以会更改,是因为客户端发生了更改。

因为具体的服务器“FirstServer”和“SecondServer”实现了“ClientServer”接口,所以它们也依赖于该接口。而且因为继承是比组合更强的依赖关系,具体的服务器类对接口的依赖性比客户端类更大。

这就是为什么依赖关系被反转了。具体的服务器类现在依赖于“Client-ClientServer”集合。

因此,对于您的问题的答案是:您无法在另一个类中创建您的类时达到依赖反转原则。但是,通过定义一个接口并注入继承此接口的具体类,您可以使用组合来实现依赖反转原则。


如果我需要使用.NET Framework中的一个类,我是否需要将其注入到该类中? - VansFannel

3

引用自Wikipedia:

A. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。

B. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

你说过:

这意味着对象应该已经实例化并传递给另一个对象

依赖倒置原则并不反对像类构造函数这样的编程实现细节,它们旨在初始化正在构造的对象。

除非您将构造函数参数定义为实现而不是抽象,和/或被注入的依赖关系比目标依赖关系更高层次,否则您不会违反整个原则。


1
了解为什么我的回答被踩可以帮助我改进,谢谢。 - Matías Fidemraizer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接