0

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

这种设计模式真的很容易理解,实际上没有什么,对象要求值而不是自己创建它们。

但是,现在我正在考虑如何使用我当前正在开发的应用程序,我意识到它有一些复杂性。想象以下示例:

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 之外实例化,因此该依赖已移至另一个类。这将重复许多层,并且在某些点上必须进行实例化。

现在的问题是,当使用依赖注入时,你是否会不断地通过所有这些层传递依赖关系?这不会降低代码的可读性和调试时间吗?当您到达“顶层”层时,您将如何对该类进行单元测试?

4

3 回答 3

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停止。

代码的可读性

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

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

单元测试

我假设“顶层”层是指PaymentHandler班级。在这个例子中,我们可以创建一个存根CustomerRepository类并让它返回一个Customer我们可以检查的对象,然后将存根传递给 stub 以PaymentHandler检查是否收取了正确的金额。

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

为什么接口

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

希望这可以帮助。

于 2013-04-08T18:12:14.890 回答
3

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

可以在这里找到一个很好的讨论:为什么我需要一个 IoC 容器而不是简单的 DI 代码?

于 2013-04-08T13:00:48.383 回答
1

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

正确使用,您将拥有一个组合根,其中所有依赖项都被解析和注入。在这里使用 IoC 容器将解决依赖关系并为您将它们向下传递到各个层。

这与许多人认为是反模式的服务位置模式直接相反。

使用组合根不应降低您的代码的可读性/可理解性,因为具有清晰和相关依赖关系的精心设计的类应该是合理的自文档化。我不确定对组合根进行单元测试。它有一个谨慎的角色,所以它应该是可测试的。

于 2013-04-09T08:23:29.490 回答