簡體   English   中英

服務定位器和依賴注入

[英]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的實例來向現有上下文添加其他工作,則可以傳入特定實例。

使用您的第一個示例,容器將構造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 兩個實現都在容器中指定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM