[英]Service locator and dependency injection
我認為普遍認為以下是不好的
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配置可以使用模擬設置所有內容 - 並且它易於使用。
編輯:
我將試着通過一個不同的例子澄清我的困惑。 假設我有一個帶有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
- 但這是一個偏好問題,而且與之前的討論無關。
我解決這個問題的方法是讓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的實例來向現有上下文添加其他工作,則可以傳入特定實例。
使用您的第一個示例,容器將構造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
。 兩個實現都在容器中指定。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.