- 在类外创建对象并将其传递给类。(依赖)
- 在类内部创建对象(组合)。
你的困惑来自于对组合的理解。被另一个对象拥有的对象取决于拥有对象的生命周期。这并不意味着你必须在拥有类内创建拥有对象。
如果你在一个类中创建对象,这个类就与创建的类耦合紧密。你无法在不改变创建其他类的类的情况下交换实现。
上面的图片中,你有一个使用类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方法或构造函数注入它。这样可以将实现类隐藏在接口后面。
在第二张图片中,我们保护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”集合。
因此,对于您的问题的答案是:您无法在另一个类中创建您的类时达到依赖反转原则。但是,通过定义一个接口并注入继承此接口的具体类,您可以使用组合来实现依赖反转原则。
引用自Wikipedia:
A. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
B. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
你说过:
这意味着对象应该已经实例化并传递给另一个对象
依赖倒置原则并不反对像类构造函数这样的编程实现细节,它们旨在初始化正在构造的对象。
除非您将构造函数参数定义为实现而不是抽象,和/或被注入的依赖关系比目标依赖关系更高层次,否则您不会违反整个原则。