依赖注入进一步向下“链”

时间:2013-04-08 12:48:20

标签: c# java unit-testing design-patterns dependency-injection

我一直在阅读如何编写可测试代码并偶然发现依赖注入设计模式。

这种设计模式非常容易理解,实际上没有任何东西,对象要求的是值而不是自己创建它们。

然而,现在我正在考虑如何使用这个应用程序即时工作,我意识到它有一些复杂性。想象一下以下示例:

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类之外实例化,因此这种依赖性已转移到另一个类。这将重复多层次,并且在某些时候必须进行实例化。

现在问题是,在使用依赖注入时,是否继续通过所有这些层传递依赖项?这会不会使代码的可读性降低,调试时间也更长;当你到达“顶层”时,你将如何对该类进行单元测试?

3 个答案:

答案 0 :(得分:4)

我希望我能正确理解你的问题。

注入依赖关系

不,我们不会通过所有图层传递依赖项。我们只将它们传递给直接与他们交谈的图层。例如:

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的注入在存储库层停止。

代码的可读性

在没有框架或库的情况下进行手动注入时,我们最终可能会使用包含许多样板代码的Factory类,例如return new D(new C(new B(), new A());,这些代码在某些时候可能不太可读。为了解决这个问题,我们倾向于使用像 Guice 这样的DI框架来避免编写这么多工厂。

但是,对于实际执行工作/业务逻辑的类,它们应该更具可读性和可理解性,因为它们只与他们的直接协作者交谈并完成他们需要做的工作。

单元测试

我假设“Top”图层是指PaymentHandler类。在这个例子中,我们可以创建一个stub CustomerRepository类,让它返回一个我们可以检查的Customer对象,然后将存根传递给PaymentHandler以检查是否正确的数量是充电。

一般的想法是传递假合作者来控制他们的输出,这样我们就可以安全地断言被测试类的行为(在这个例子中是PaymentHandler类)。

为什么接口

正如上面的评论中所提到的,更可取的是依赖于接口而不是具体的类,它们提供了更好的可测试性(易于模拟/存根)和更容易的调试。

希望这有帮助。

答案 1 :(得分:3)

嗯,是的,这意味着你必须在所有层上传递依赖关系。然而,这就是Inversion of Control容器派上用场的地方。它们允许您注册系统中的所有组件(类)。然后,您可以向IoC容器询问class B的实例(在您的示例中),它会自动调用正确的构造函数,以便您自动创建构造函数所依赖的任何对象(在您的情况下为class A)。

可在此处找到一个很好的讨论:Why do I need an IoC container as opposed to straightforward DI code?

答案 2 :(得分:1)

IMO,您的问题表明您了解模式。

正确使用,您将拥有Composition Root,其中所有依赖项都已解析并注入。在这里使用IoC容器可以解决依赖关系并将它们传递给你。

这与服务地点模式直接相反,服务地点模式被许多人视为反模式。

使用Composition Root不应该使您的代码不易读/易理解,因为具有明确且相关依赖性的精心设计的类应该是合理的自我记录。我不确定单元测试组合根。它具有谨慎的作用,因此它应该是可测试的。