2013-04-08 67 views
0

我一直在阅读如何编写可测试代码,并偶然发现依赖注入设计模式。依赖注入进一步向下“链”

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

然而,现在我在想如何能够使用的应用IM currenty工作我知道有一些并发症它。想象一下下面的例子:

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()现在很容易,因为我可以创建一个的模拟,并有getValue()回我想做的事情。

B类的的依赖关系是通过构造函数注入,但是这也意味着A有B类外被实例化因此这种依赖已经转移到另一个类来代替。这将会重复许多层,并且在某个点上实例化必须完成。

现在的问题是,在使用依赖注入的时候,是否不断传递所有这些层次的依赖关系?这不会让代码更少可读性和更多的时间来调试吗?当你到达“顶层”时,你将如何测试该课程?

+0

这不是依赖注入。你必须在实现它之后创建接口,而不是私有A在你的类b中使用私有IAia,然后传递构造函数对象A并将它分配给你的变量ia – 2013-04-08 12:57:25

回答

3

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

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

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());这在某些时候可以少读工厂类。为了解决这个问题,我们倾向于使用DI框架如Guice来避免编写这么多的工厂。

然而,对于类,实际上做的工作/业务逻辑,他们应该更易于阅读和理解,因为他们只跟他们的直接合作者,做他们需要做的工作。

单元测试

我认为通过“顶”层你的意思PaymentHandler类。在本例中,我们可以创建一个存根CustomerRepository类,并返回一个Customer对象,我们可以检查该对象,然后将存根传递给PaymentHandler以检查是否收取正确的金额。

总体思路是通过假合作者来控制他们的输出,以便我们可以安全地断言受测试的类的行为(在本例中为PaymentHandler类)。

为什么接口

如上注释提到的,更优选的是依赖于接口代替混凝土类,它们提供更好的可测试性(易于嘲笑/存根)和更容易调试。

希望这会有所帮助。

1

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

正确使用,您将有一个Composition Root所有依赖关系被解析和注入。在这里使用IoC容器将解决依赖关系,并将它们传递给您的图层。

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

组合根的使用不应该让您的代码更少可读/可理解,因为设计良好的具有清晰相关依赖性的类应该是合理的自我记录。我不确定单元测试组合根。它有一个谨慎的角色,所以它应该是可测试的。