【问题标题】:Service locator and dependency injection服务定位器和依赖注入
【发布时间】:2014-05-10 16:30:25
【问题描述】:

我认为人们普遍认为跟随是不好的

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 或在需要时创建新的 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 控制器来解析实例依赖关系,还是有处理这些情况的首选方法?还是我只是做错了什么……?

【问题讨论】:

  • 回答这个问题有点困难,因为这两个例子都有内置的问题:UnitOfWork 的第一个例子有一个问题(正如你已经指出的那样)它没有 任何东西;它完全是多余的,可以从您的代码库中删除。第二个示例带有内置问题(由于框架限制),Form 必须 具有默认构造函数。但是,总有办法解决这些问题。 Composition Root 是您可能会发现有用的一个核心概念。
  • 你说话的方式我知道你会真正受益(就像我以外的许多其他人一样)尽快购买 manning.com/seemann(并在你阅读 Mark 投票最多的答案时)等待它到达)
  • 看起来很有趣,而且物有所值——巧合的是,他实际上就住在我附近:)

标签: c# dependency-injection inversion-of-control


【解决方案1】:

关于问题的第一部分:

消费者当然可以在构造函数中要求 IService 也一样,但是当层次结构变为 更深。

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

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

这只会发生在您的composition root 中。因此,如果它是一个 MVC 应用程序,您已经以某种方式将 MVC 框架配置为在需要实例化控制器时使用您的 DI 配置,因此框架在内部决定(从路由)它需要一个 MyController,并且它执行类似 @987654327 的操作@。所以服务位置只在上面使用,而不是在你的控制器或其他任何地方。

关于问题的MyController 部分:

无法真正获得与前一部分的联系,但您仍然可以使用构造函数注入。没有静态类(没有注入,因此不能为了测试目的而交换或模拟出来),没有服务位置。

[作为旁注,您甚至可以避免关于工作单元的额外代码(可能您使用的 ORM 有一个并且您已经在 IRepositories 的实现中使用它)。也许您的存储库可以有一个 SaveChanges 方法,它将调用 unitOfWork 的 SaveChanges - 但这是一个偏好问题,与前面的讨论无关]。

【讨论】:

  • 我有点明白你在说什么,但它仍然让我对你在一个没有通过 IoC 实例化的类中的情况感到困惑。我已经编辑了我的问题以反映这一点。拥有数千个课程的解决方案怎么样?如果所有类都需要一个接口并且必须绑定到 IoC 中,那么组合是否会变得非常复杂,可能会因为缺少绑定和/或循环依赖而导致运行时错误?
  • @sondergard 有一些方法可以通过使用约定来避免接口与实现的显式绑定(检查您的 DI 框架以了解如何执行此操作)。您可以在不同的类/模块中组织您的 DI 配置,以便每个都是可管理的。未从组合根实例化的类,例如通过工厂实例化,从这些工厂进行管理。同样,创建/管理对象生命周期的责任被封装在一个地方,而不是分散在这些类的用户内部。
【解决方案2】:

使用您的第一个示例,容器将同时构造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。两种实现都在容器中指定。

【讨论】:

    【解决方案3】:

    我解决这个问题的方法是有一个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 的实例来向现有上下文添加额外的工作,您可以传入一个特定的实例。

    【讨论】:

      猜你喜欢
      • 2013-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-30
      • 1970-01-01
      • 2013-06-28
      相关资源
      最近更新 更多