依赖注入在“链”的更下游
本文关键字:注入 依赖 | 更新日期: 2023-09-27 18:08:16
我一直在阅读如何编写可测试代码,偶然发现了依赖注入设计模式。
这个设计模式真的很容易理解,它真的没有什么,对象请求值,而不是自己创建它们。
然而,现在我正在考虑如何在我正在开发的应用程序中使用它,我意识到它有一些复杂性。想象一下下面的例子:
public class A{
public string getValue(){
return "abc";
}
}
public class B{
private A a;
public B(A a){
this.a=a;
}
public void someMethod(){
String str = a.getValue();
}
}
单元测试someMethod ()
现在很容易,因为我可以创建a的模拟,并让getValue()
返回我想要的任何内容。
类B对A的依赖是通过构造函数注入的,但这意味着A必须在类B之外实例化,因此该依赖已经转移到另一个类中。这将重复许多层,在某些点实例化必须完成。
现在的问题是,当使用依赖注入时,你是否一直通过所有这些层传递依赖?这会不会使代码的可读性降低,并且花费更多的时间来调试?当您到达"顶层"时,您将如何对该类进行单元测试?
希望我正确理解了你的问题。
注入依赖项
不,我们不传递依赖关系到所有的层。我们只将它们传递给直接与它们对话的图层。例如:
public class PaymentHandler {
private customerRepository;
public PaymentHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public void handlePayment(CustomerId customerId, Money amount) {
Customer customer = customerRepository.findById(customerId);
customer.charge(amount);
}
}
public interface CustomerRepository {
public Customer findById(CustomerId customerId);
}
public class DefaultCustomerRepository implements CustomerRepository {
private Database database;
public CustomerRepository(Database database) {
this.database = database;
}
public Customer findById(CustomerId customerId) {
Result result = database.executeQuery(...);
// do some logic here
return customer;
}
}
public interface Database {
public Result executeQuery(Query query);
}
PaymentHandler
不知道Database
的存在,它只与CustomerRepository
对话。Database
的注入在存储库层停止。
代码可读性
在没有框架或库帮助的情况下进行手工注入时,我们最终可能会得到包含许多样板代码的工厂类,比如return new D(new C(new B(), new A());
,这些代码在某种程度上可能会降低可读性。为了解决这个问题,我们倾向于使用像Guice这样的DI框架来避免编写这么多工厂。
然而,对于实际执行工作/业务逻辑的类,它们应该更具可读性和可理解性,因为它们只与直接协作者对话并完成它们需要做的工作。
单元测试
我假设你所说的"Top"层是指PaymentHandler
类。在这个例子中,我们可以创建一个存根CustomerRepository
类,并让它返回一个Customer
对象,然后将存根传递给PaymentHandler
来检查是否收取了正确的金额。
PaymentHandler
类)。<为什么接口/strong>
正如在上面的评论中提到的,依赖接口而不是具体类更可取,它们提供了更好的可测试性(易于模拟/存根)和更容易调试。
嗯,是的,这意味着你必须传递所有层的依赖关系。然而,这正是控制反转容器派上用场的地方。它们允许您注册系统中的所有组件(类)。然后,您可以向IoC容器请求class B
的实例(在您的示例中),它将自动为您调用正确的构造函数,自动创建构造函数所依赖的任何对象(在您的示例中是class A
)。
可以在这里找到一个很好的讨论:为什么我需要一个IoC容器而不是直接的DI代码?
在我看来,你的问题表明你理解这个模式。
正确使用,您将拥有一个组合根,其中所有依赖项都被解析和注入。在这里使用IoC容器可以解析依赖关系,并将它们传递到各个层。
这与许多人认为是反模式的服务位置模式直接相反。
使用组合根不应该使你的代码可读性/可理解性降低,因为设计良好的类具有清晰和相关的依赖关系,应该合理地自文档化。我不确定单元测试的组成根。