Converting from service-locator to dependency injection

时间:2015-09-25 16:36:40

标签: design-patterns dependency-injection

I'm the process of cleaning up some code. I have decided to do this because it is very likely that new features will be requested in the near future and the code is hard to grok (not only because it uses a dependency container as a service locator).

To a fair extent, I understand why SL is terrible for dependency management and I've declared all dependencies of a method in the operation's signature even before I knew what OOP is.

When working with frameworks like AngularJS, you get dependency injection for free. This is not the case with my current project. The only way I can think of instantiating the application "the right way" is by service-locating everything in the main method. Please point me in the right direction toward a more elegant implementation of dependency injection.

This is my current approach:

function main()
    container = new Dic
    container->set(A, lazyNew(foo/bar/A)
    container->set(B, lazyNew(A), depends-on:[A])
    container->set(App, lazyNew(App), depends-on:[A, B])
    // more configuration code
    app = container->get(App)
    app.main()

The flaw here is that I'm still using the container as a service locator. The only benefit is that the dependency graph is "resolved automatically" (tedious configuration and declaration of dependencies for every instance). Another pro is that instantiation happens in one place. Please help me understand how to take this to the next level of awesome.

2 个答案:

答案 0 :(得分:1)

由于您仅使用主方法使用容器,因此使用容器作为服务定位器。

如果您在类(而非主类)中使用容器,则只会将其视为服务位置。

实际上,在进行依赖注入时,我们必须在main方法(或其他类型的应用程序的其他入口点)中创建对象图。这个地方叫做Composition Root

问题:您是否在课程中使用构造函数注入?我假设你这样做。

在我看来,DI(或者组合根)的更优雅的实现是不使用DI容器,而是使用Pure DI。有关原因,请参阅我的文章here

<强>更新

以下是如何使用构造函数注入的示例。

我将使用C#语言。

public interface IDependencyA {}
public interface IDependencyB {}
public interface IDependencyC {}
public interface IBackEnd {}

public class DependencyC : IDependencyC {}
public class DependencyA : IDependencyA {}

public class DependencyB : IDependencyB
{
    private readonly IBackEnd m_BackEnd;
    public DependencyB(IBackEnd back_end)
    {
        m_BackEnd = back_end;
    }
}

public class BackEnd : IBackEnd
{
    private readonly IDependencyC m_DependencyC;
    public BackEnd(IDependencyC dependency_c)
    {
        m_DependencyC = dependency_c;
    }
}

public class App
{
    private readonly IDependencyA m_DependencyA;
    private readonly IDependencyB m_DependencyB;
    public App(IDependencyA dependency_a, IDependencyB dependency_b)
    {
        m_DependencyA = dependency_a;
        m_DependencyB = dependency_b;
    }
}

以下是如何使用Unity容器构建对象图:

UnityContainer container = new UnityContainer();

container.RegisterType<IDependencyA, DependencyA>();
container.RegisterType<IDependencyB, DependencyB>();
container.RegisterType<IDependencyC, DependencyC>();
container.RegisterType<IBackEnd, BackEnd>();

App application = container.Resolve<App>();

以下是使用Pure DI创建对象图的方法:

App application = new App(
    new DependencyA(),
    new DependencyB(
        new BackEnd(
            new DependencyC())));

答案 1 :(得分:0)

一般而言,您需要设置某种基础结构来声明依赖关系并抽象声明的服务或模块的实例化。

考虑到问题中的Angular示例,如果您退后一步并反思它,一切都通过angular.module函数以角度进行。

直观地说,这必须是&#34; app&#34;然后,应用程序的较小组件(指令,服务,控制器)被声明并注册到底层依赖注入容器。毕竟,所有这些都将一系列依赖关系作为第二个参数:

var injectable3, injectable2, injectable;

injectable  =    angular.module(    'myApp', [dependency, ..., fn(d1, ...){}]);
injectable2 = injectable.controller('foo',   [dependency, ..., fn(d2, ...){}]);
injectable3 = injectable.service(   'bar',   [dependency, ..., fn(d3, ...){}]);

看起来很神奇,但事实并非如此。他们都通过某种依赖注入容器进行交谈。这实际上在以下评论中说明:loader.js

检查angular中的一些核心方法:

/**
 * @name angular.bootstrap
 * @returns {auto.$injector} Returns the newly created injector for app.
 */
function bootstrap(element, modules, config) {
    // (...)
    var doBootstrap = function(element, modules, config) {
        modules = modules || [];
        modules.unshift(['$provide', function ($provide) {
            $provide.value('$rootElement', element);
        }]);
        if (config.debugInfoEnabled) {
            modules.push(['$compileProvider', function ($compileProvider) {
                $compileProvider.debugInfoEnabled(true);
            }]);
        }
        // (...)
        modules.unshift('ng');
        var injector = createInjector(modules, config.strictDi);
        injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
                function bootstrapApply(scope, element, compile, injector) {
                    scope.$apply(function () {
                        element.data('$injector', injector);
                        compile(element)(scope);
                    });
                }]
        );
        return injector;
    };
    // (...)
    if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
        return doBootstrap();
    }
    // (...)
}

如果您不记得,引导方法负责初始化<body ng-app="coffeeShop">等应用程序。上面的摘录说明了为每个应用程序声明了其他依赖项。下一步(createInjector)是确定依赖关系的创建位置和方式。

总而言之,一种方法是声明提供者,用一些定位器注册它们,然后声明依赖于提供者的模块,最后在稍后阶段引导整个事物。

我想我已经为这个星期天早上准备好了。

相关问题