服务定位器和依赖注入
本文关键字:注入 依赖 定位器 服务 | 更新日期: 2023-09-27 18:02:37
我认为人们普遍认为以下行为是不好的
public class Foo
{
private IService _service;
public Foo()
{
_service = IocContainer.Resolve<IService>();
}
}
和以下内容是首选(依赖注入)
public class Foo
{
private IService _service;
public Foo(IService service)
{
}
}
然而,现在由消费者来提供服务。当然,消费者也可以在构造函数中要求IService,但是当层次结构变得更深时,这似乎很烦人。在某些时候,有人需要从IoC容器请求isservice——但是什么时候? ?
我工作场所的一位前同事为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控制器来解决依赖关系的实例,或者是否有处理这些情况的首选方法?还是我做错了什么?
关于问题的第一部分:
消费者当然可以在构造函数中要求IService也一样,但是当等级制度变成更深。
不,消费者不需要IService
,它需要IFoo
。它不知道它将获得的IFoo
依赖于IService
,只有您的DI配置知道。所以,不用担心,你最终不会得到你描述的的依赖层次结构。
在某些时候,有人需要从IoC请求isservice集装箱——但是什么时候?
这只会发生在你的组合根目录中。因此,如果它是一个MVC应用程序,您已经以某种方式配置了MVC框架,以便在需要实例化控制器时使用您的DI配置,因此框架内部决定(从路由)它需要MyController
,并且它做了类似resolver.Get<MyController>()
的事情。所以service location只在上面使用,而不是在你的控制器或其他任何地方。
关于MyController
部分问题:
不能真正获得与前一部分的连接,但仍然可以使用构造函数注入。没有静态类(没有注入,因此不能为了测试目的而交换或模拟),没有服务位置。
作为旁注,您甚至可以避免关于工作单元的额外代码(可能您使用的ORM有一个,并且您已经在实现IRepositories
时使用了它)。也许您的存储库可以有一个SaveChanges
方法,它将调用unitOfWork的SaveChanges
——但这是一个偏好问题,与前面的讨论无关。
使用您的第一个示例,容器将构造IFoo
和IService
。下面是一些真实的代码来说明:
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的实例来向现有上下文添加额外的工作,您可以传入一个特定的实例。