逐渐将代码库移至依赖项注入容器


9

我有一个很大的代码库,其中包含许多“反模式”单例,带有静态方法的实用程序类以及使用new关键字创建自己的依赖项的类。这使得代码很难测试。

我想逐步将代码迁移到依赖项注入容器(在我的情况下是Guice,因为它是一个GWT项目)。从我对依赖注入的理解来看,它全有还是全无。所有类都由Spring / Guice管理,或者没有。由于代码库很大,因此我无法在一夜之间转换代码。所以我需要一种逐渐做到的方法。

问题是,当我从一个需要注入其他类的类开始时,我不能@Inject在那些类中使用简单的类,因为那些类尚未由容器管理。因此,这将创建一条长链,直至未注入任何地方的“顶级”类。

我看到的唯一方法是Injector暂时使/应用程序上下文通过单例全局可用,以便其他类可以从中获取托管bean。但这与不composition root向应用程序公开的重要思想相矛盾。

另一种方法是自下而上:从“高级”类开始,将它们包含在依赖项注入容器中,然后慢慢移至“较小”类。但是然后我不得不等待很长时间,因为我可以测试仍然依赖于全局变量/静态变量的较小的类。

实现这种逐步迁移的方式是什么?

PS问题:依赖注入的渐进方法在标题上相似,但是它不能回答我的问题。


1
您是否想从拥有硬耦合类直接转到使用依赖项注入容器?首先,您必须使用接口进行一些重构以解耦类,甚至在考虑与任何依赖项注入框架或工具结婚之前。
图兰斯·科尔多瓦

很公平。因此,我有一些实用程序类A在其他30个类中使用(其中一个是B类),使它们不可测试。我想让A类成为B类的构造函数依赖项。我必须更改对构造函数的所有调用,并将A传递给内部。但是我应该从哪里得到呢?
damluar '16

如果B是您可以应用DI入门的最小单元,那么无论您目前在哪里构造B,只要在A没有依赖性的情况下构造B,我都认为没有什么害处。球滚动后,您可以返回并对其进行重构。
安迪·亨特

@damluar开始的工厂是?
图兰斯·科尔多瓦

1
@damluar类并不会因为依赖实用程序类而变得不可测试。这只是意味着您的单位比您想要的要大。如果您可以单独测试实用程序类,则可以在其他所有30个类的测试中使用它。阅读有关单元测试的Martin Fowlers博客。
gbjbaanb

Answers:


2

对不起C#是我所选择的语言,我可以读Java,但可能会杀猪的语法试图写它...相同的概念之间适用C#Java的,所以希望这将显示你如何能逐渐将你的代码库更加的步骤可测试的。

鉴于:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

可以很容易地重构为使用DI,而无需使用IOC容器-您甚至可能将其分解为几个步骤:

(可能)第一步-引入依赖关系,但不更改调用(UI)代码:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

重构2(或首先重构,如果实现IOC容器并立即更改调用代码):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

从技术上讲,步骤2可以自己完成-但(可能)要做很多工作-取决于当前有多少类正在“更新”您要寻找DI的功能。

考虑使用第1步->第2步路线-您可以创建Foo独立于的单元测试Bar。而在步骤1重构之前,如果不使用这两个类的实际实现就很难完成它。执行步骤1->步骤2(而不是立即执行步骤2)将允许随着时间的推移进行较小的更改,并且您已经开始使用测试工具,以便更好地确保重构工作不会产生任何后果。


0

无论您使用的是Java,PHP还是C#,概念都是相同的。YouTube影片中的Gemma Anible很好地解决了这个问题:

https://www.youtube.com/watch?v=Jccq_Ti8Lck(PHP,对不起!)

您将无法测试的代码替换为“ facade”(由于缺乏更好的术语),从而调用了新的,可测试的代码。然后,您可以逐渐用注入的服务替换较早的呼叫。我过去做过,而且效果很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.