服务定位器和依赖注入

我认为普遍认为以下是不好的

public class Foo
{
    private IService _service;
    public Foo()
    {
        _service = IocContainer.Resolve<IService>();
    }
}

并且以下是优选的(依赖注入)

public class Foo
{
    private IService _service;
    public Foo(IService service)
    {
    }
}

但是,现在取决于消费者提供服务。 消费者当然也可以在构造函数中要求IService,但是当层次结构变得更深时,它似乎很烦人。 在某些时候,有人需要从IoC容器请求IService - 但是何时......?

我的工作场所的一位前同事已经为UoW / Repository模式编写了一个UnitOfWork类(使用Microsoft ServiceLocator):

public static UnitOfWork
{
    public static IUnitOfWork Current
    {
        get { return ServiceLocator.Current.GetInstance<IUnitOfWork>(); }
    }

    public static void Commit()
    {
        Current.Commit();
    }

    public static void Dispose()
    {
        Current.Dispose();
    }

    public static IRepository<T> GetRepository<T>() where T : class
    {
        return ServiceLocator.Current.GetInstance<IRepository>();
    }
}

并使用Ninject连接IoC,以便IRepository的请求可以找到当前UoW或创建一个新的(如果需要)(如果处理电流)。 用法变成了

public class MyController
{    
    public void RunTasks()
    {
        var rep = UnitOfWork.GetRepository<Tasks>();
        var newTasks = from t in rep.GetAll()
                       where t.IsCompleted == false
                       select t;

        foreach (var task in newTasks)
        {
            // Do something
        }

        UnitOfWork.Commit();
    }
}

然而,它仍然受到静态IoC(服务定位器)类的影响,但会不会有更智能的解决方案? 在这种情况下,不需要了解内部依赖关系(静态类没有逻辑),并且出于测试目的,备用IoC配置可以使用mock设置所有内容 - 并且易于使用。

编辑:

我会试着用另一个例子来澄清我的困惑。 假设我有一个带有MainWindow类的标准winforms应用程序。 当用户点击一个按钮时,我需要从数据库中加载一些数据,并将它传递给一个将处理数据的类:

public class MainWindow : Form
{
    public MainWindow()
    {
    }

    private void OnUserCalculateClick(object sender, EventArgs args)
    {
        // Get UoW to connect to DB
        // Get instance of processor
    }
}

我将如何获得处理器实例和工作单元? 它可以注入表单类吗?

我想我的问题归结为:如果我在一个没有Ioc构建的类中,它可能是一个winform,一个ria服务类等 - 可以引用服务定位器/ IoC控制器来解析依赖关系的实例,还是有处理这些案件的首选方法? 或者我只是做错了什么...?


关于问题的第一部分:

消费者当然也可以在构造函数中要求IService,但是当层次结构变得更深时,它似乎很烦人。

不,消费者不需要IService ,它需要IFoo 。 它不知道它将得到的IFoo依赖于IService ,只有你的DI配置知道这一点。 所以,不要担心, 你不会得到你描述的这个依赖层次结构

在某些时候,有人需要从IoC容器请求IService - 但是何时......?

这只会发生在你的作文根。 因此,如果它是一个MVC应用程序,那么当需要实例化控制器时,您已经以某种方式配置了MVC框架以使用您的DI配置,因此框架在内部决定(从路由)它需要MyController ,并且它执行类似resolver.Get<MyController>() 。 所以服务地点只在那里使用,而不是在你的控制器或其他地方。

关于问题的MyController部分:

无法真正获得与前一部分的连接,但仍然可以使用构造函数注入。 没有静态类(没有被注入,所以不能被交换或嘲笑出于测试目的),没有服务位置。

[作为一个方面说明,你甚至可以避免关于工作单元的额外代码(可能你使用的ORM有一个,而且你已经在实施IRepositories使用它)。 也许你的仓库可以有一个SaveChanges方法,它会调用unitOfWork的SaveChanges - 但这是一个偏好问题,与前面的讨论无关)。


使用你的第一个例子,容器将构建IFooIService 。 下面是一些真实的代码来说明:

        container.RegisterType<ISubmittingService, GnipSubmittingService>(
            new DisposingTransientLifetimeManager(),
            new InjectionConstructor(
                typeof (IGnipHistoricConnection),
                typeof (IUserDataInterface),
                new EstimateVerboseLoggingService.TitleBuilder(),
                new EstimateVerboseLoggingService.FixedEndDateBuilder(),
                typeof (ISendEmailService),
                addresses,
                typeof (ILog)
                )
            );

        container.RegisterType<IEstimateService, EstimateVerboseLoggingService>(
            new DisposingTransientLifetimeManager(),
            new InjectionConstructor(
                typeof(IEstimateDataInterface),
                typeof(ISubmittingService),
                typeof(ILog)
                )
            );

...

    public EstimateVerboseLoggingService(
        IEstimateDataInterface estimateData,
        ISubmittingService submittingService,
        ILog log)
    {
        _estimateData = estimateData;
        _submittingService = submittingService;
        _log = log;
    }

...

    public GnipSubmittingService(
        IGnipHistoricConnection gnip,
        IUserDataInterface userDb,
        IBuilder<string, int> titleBuilder,
        IBuilder<DateTime, DateTime> endDateBuilder,
        ISendEmailService emailService,
        IEnumerable<MailAddress> errorEmailRecipients,
        ILog log)
    {
        _gnip = gnip;
        _userDb = userDb;
        _titleBuilder = titleBuilder;
        _endDateBuilder = endDateBuilder;
        _emailService = emailService;
        _errorEmailRecipients = errorEmailRecipients;
        _log = log;
    }

在此代码中, EstimateVerboseLoggingService使用ISubmitingService 。 这两个实现都在容器中指定。


我解决这个问题的方法是有一个UnitOfWorkFactory ,它有一个Create方法来创建你的UnitOfWork

public interface IUnitOfWorkFactory
{
    IUnitOfWork Create();
}

public interface IUnitOfWork : IDisposable
{
    T GetRepository<T>();
    void Commit();
}

public class MyController
{    
    private readonly IUnitOfWorkFactory _unitOfWorkFactory;

    public MyController(IUnitOfWorkFactory unitOfWorkFactory)
    {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void RunTasks()
    {
        using (var unitOfWork = _unitOfWorkFactory.Create())
        {
            var rep = UnitOfWork.GetRepository<Tasks>();
            var newTasks = from t in rep.GetAll()
                           where t.IsCompleted == false
                           select t;

            foreach (var task in newTasks)
            {
                // Do something
            }               

            unitOfWork.Commit();
        }
    }
}  

拥有工厂的好处是它可以让用户(控制器)控制工作单元的创建和销毁。

这也使得单元测试更容易,因为您不需要使用IoC进行测试。 我也不喜欢拥有全局上下文(比如UnitOfWork.Current ),因为很难确定何时将处置UoW或承诺。

如果另一个类需要一个UoW的实例来向现有的上下文添加额外的工作,则可以传入特定的实例。

链接地址: http://www.djcxy.com/p/77713.html

上一篇: Service locator and dependency injection

下一篇: Replacing Singletons With ServiceLocators when using Dependency Injection