简体   繁体   English

Seemann的依赖注入,“三个呼叫模式”与服务定位器Anit-Pattern

[英]Seemann's Dependency Injection, “Three Calls Pattern” vs Service Locator Anit-Pattern

I have created a WinForms MVC application using Dependency Injection (DI) and Ninject as the DI Container. 我使用依赖注入(DI)和Ninject作为DI容器创建了一个WinForms MVC应用程序。 The basic architecture is as follows 基本架构如下

Program.cs (the main entry point of the WinForms application): Program.cs (WinForms应用程序的主要入口点):

static class Program
{
    [STAThread]
    static void Main()
    {
        ...

        CompositionRoot.Initialize(new DependencyModule());
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(CompositionRoot.Resolve<ApplicationShellView>());
    }
}

DependencyModule.cs DependencyModule.cs

public class DependencyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IApplicationShellView>().To<ApplicationShellView>();
        Bind<IDocumentController>().To<SpreadsheetController>();
        Bind<ISpreadsheetView>().To<SpreadsheetView>();
    }
}

CompositionRoot.cs CompositionRoot.cs

public class CompositionRoot
{
    private static IKernel ninjectKernel;

    public static void Initialize(INinjectModule module)
    {
        ninjectKernel = new StandardKernel(module);
    }

    public static T Resolve<T>()
    {
        return ninjectKernel.Get<T>();
    }

    public static IEnumerable<T> ResolveAll<T>()
    {
        return ninjectKernel.GetAll<T>();
    }
}

ApplicationShellView.cs (the main form of the application) ApplicationShellView.cs应用程序的主要形式)

public partial class ApplicationShellView : C1RibbonForm, IApplicationShellView
{
    private ApplicationShellController controller; 

    public ApplicationShellView()
    {
        this.controller = new ApplicationShellController(this);
        InitializeComponent();
    }

    public void InitializeView()
    {
        dockPanel.Extender.FloatWindowFactory = new CustomFloatWindowFactory();
        dockPanel.Theme = vS2012LightTheme;
    }

    private void ribbonButtonTest_Click(object sender, EventArgs e)
    {
        controller.OpenNewSpreadsheet();
    }

    public DockPanel DockPanel
    {
        get { return dockPanel; }
    }
}

where 哪里

public interface IApplicationShellView
{
    void InitializeView();
    DockPanel DockPanel { get; }
}

ApplicationShellController.cs ApplicationShellController.cs

public class ApplicationShellController
{
    private IApplicationShellView shellView;

    public ApplicationShellController(IApplicationShellView view)
    {
        this.shellView = view;
    }

    public void OpenNewSpreadsheet(DockState dockState = DockState.Document)
    {
        SpreadsheetController controller = (SpreadsheetController)GetDocumentController("new.xlsx");
        SpreadsheetView view = (SpreadsheetView)controller.New("new.xlsx");
        view.Show(shellView.DockPanel, dockState);
    }

    private IDocumentController GetDocumentController(string path)
    {
        return CompositionRoot.ResolveAll<IDocumentController>()
            .SingleOrDefault(provider => provider.Handles(path));
    }

    public IApplicationShellView ShellView { get { return shellView; } }
}

SpreadsheetController.cs SpreadsheetController.cs

public class SpreadsheetController : IDocumentController 
{
    private ISpreadsheetView view;

    public SpreadsheetController(ISpreadsheetView view)
    {
        this.view = view;
        this.view.SetController(this);
    }

    public bool Handles(string path)
    {
        string extension = Path.GetExtension(path);
        if (!String.IsNullOrEmpty(extension))
        {
            if (FileTypes.Any(ft => ft.FileExtension.CompareNoCase(extension)))
                return true;
        }
        return false;
    }

    public void SetViewActive(bool isActive)
    {
        ((SpreadsheetView)view).ShowIcon = isActive;
    }

    public IDocumentView New(string fileName)
    {
        // Opens a new file correctly.
    }

    public IDocumentView Open(string path)
    {
        // Opens an Excel file correctly.
    }

    public IEnumerable<DocumentFileType> FileTypes
    {
        get
        {
            return new List<DocumentFileType>()
            {
                new DocumentFileType("CSV",  ".csv" ),
                new DocumentFileType("Excel", ".xls"),
                new DocumentFileType("Excel10", ".xlsx")
            };
        }
    }
}

where the implemented interface is 实现的接口在哪里

public interface IDocumentController
{
    bool Handles(string path);

    void SetViewActive(bool isActive);

    IDocumentView New(string fileName);

    IDocumentView Open(string path);

    IEnumerable<DocumentFileType> FileTypes { get; }
}

Now the view ascociated with this controller is 现在与该控制器关联的视图是

public partial class SpreadsheetView : DockContent, ISpreadsheetView
{
    private IDocumentController controller;

    public SpreadsheetView()
    {
        InitializeComponent();
    }

    private void SpreadsheetView_Activated(object sender, EventArgs e)
    {
        controller.SetViewActive(true);
    }

    private void SpreadsheetView_Deactivate(object sender, EventArgs e)
    {
        controller.SetViewActive(false);
    }

    public void SetController(IDocumentController controller)
    {
        this.controller = controller;
        Log.Trace("SpreadsheetView.SetController(): Controller set successfully");
    }

    public string DisplayName
    {
        get { return Text; }
        set { Text = value; }
    }

    public WorkbookView WorkbookView
    {
        get { return workbookView; }
        set { workbookView = value; }
    }
    ...
}

Finally the view interfaces are 最后是视图接口

public interface ISpreadsheetView : IDocumentView
{
    WorkbookView WorkbookView { get; set; } 
}

and

public interface IDocumentView
{
    void SetController(IDocumentController controller);

    string DisplayName { get; set; }

    bool StatusBarVisible { get; set; }
}

Now for my questions. 现在我的问题。 In Seemann's book "Dependency Injection in .NET" he talks about the "Three Calls Pattern" and this is what I have attempted to implement in the above. 在Seemann的书“.NET中的依赖注入”中,他谈到了“三个呼叫模式”,这是我在上面尝试实现的。 The code works, the shell view displays and via the MVC pattern my controllers correctly open views etc. However, I am confused as the above definately has the flavour of the "Service Locator Anti-Pattern". 代码工作,shell视图显示,并通过MVC模式我的控制器正确打开视图等。但是,我很困惑,因为上面肯定有“服务定位器反模式”的味道。 In chapter 3 of Seemann's book he states 在Seemann的书的第3章中他说

The COMPOSITION ROOT pattern describes where you should use a DI CONTAINER. COMPOSITION ROOT模式描述了您应该使用DI容器的位置。 However, it doesn't state how to use it. 但是,它没有说明如何使用它。 The REGISTER RESOLVE RELEASE pattern addresses this question [...] A DI CONTAINER should be used in three successive phases called Register, Resolve, and Release. REGISTER RESOLVE RELEASE模式解决了这个问题[...] DI CONTAINER应该在称为Register,Resolve和Release的三个连续阶段中使用。

In its pure form, the REGISTER RESOLVE RELEASE pattern states that you should only make a single method call in each phase. REGISTER RESOLVE RELEASE模式以纯粹的形式表明您应该只在每个阶段进行一次方法调用。 Krzysztof Kozimic calls this the Three Calls Pattern. Krzysztof Kozimic将此称为三召唤模式。

Configuring a DI CONTAINER in a single method call requires more explanation. 在单个方法调用中配置DI CONTAINER需要更多解释。 The reason that registration of components should happen in a single method call is because you should regard configuration of a DI CONTAINER as a single, atomic action. 在单个方法调用中发生组件注册的原因是因为您应该将DI CONTAINER的配置视为单个原子操作。 Once configuration is completed, the container should be regarded as read-only. 配置完成后,容器应视为只读。

This sounds like the dredded "Service locator", why is this not deemed service location? 这听起来像是“服务定位器”,为什么这不被视为服务地点?


In order to adjust my code to instead use Contstructor Injection, I changed my entry code to 为了调整我的代码而不是使用Contstructor Injection,我将我的输入代码更改为

[STAThread]
static void Main()
{
    var kernel = new StandardKernel();
    kernel.Bind(t => t.FromThisAssembly()
                      .SelectAllClasses()
                      .BindAllInterfaces());

    FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath());
    Log.LogHandler = fileLogHandler;
    Log.Trace("Program.Main(): Logging initialized");

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(kernel.Get<ApplicationShellView>());
}

using Ninject.Extensions.Conventions , I then changed ApplicationShellController in order to correct my code to inject the IDocumentController s via ctor injection: 使用Ninject.Extensions.Conventions ,然后我更改了ApplicationShellController以更正我的代码以通过ctor注入注入IDocumentController

public class ApplicationShellController
{
    private IApplicationShellView shellView;
    private IEnumerable<IDocumentController> controllers; 

    public ApplicationShellController(IApplicationShellView shellView, IEnumerable<IDocumentController> controllers)
    {
        this.shellView = shellView;
        this.controllers = controllers;
        Log.Trace("ApplicationShellController.Ctor(): Shell initialized successfully"); 
    }
    ...
}

where 哪里

public class SpreadsheetController : IDocumentController 
{
    private ISpreadsheetView view;

    public SpreadsheetController(ISpreadsheetView view)
    {
        this.view = view;
        this.view.SetController(this);
    }
    ...
}

but this leads to a circular dependency, how do I handle this? 但这导致循环依赖,我该如何处理?

Question Summary: 问题摘要:

  1. Why is my initial use of Ninject using "Thee Calls Pattern" and CompositionRoot.Resolve<T>() bad or different to the Service Locator Anti-Pattern? 为什么我最初使用Ninject使用“Thee Calls Pattern”和CompositionRoot.Resolve<T>()与服务定位器反模式不同或不同?
  2. How can I resolve the circular dependency issue above if I want to switch to pure ctor injection? 如果我想切换到纯ctor注入,如何解决上面的循环依赖问题?

Thanks very much for your time. 非常感谢你花时间陪伴。

At some point in the process, you have to use service location. 在此过程中的某个时刻,您必须使用服务位置。 However, the difference between DI and SL is that in SL, you are resolving your services at the point they are requested, whereas in DI you resolve them in some kind of factory (such as a controller factory) and then construct your objects and pass the reference in. 但是,DI和SL之间的区别在于,在SL中,您将在请求时解析您的服务,而在DI中,您可以在某种工厂(例如控制器工厂)中解析它们,然后构建您的对象并通过参考文献。

You should create some kind of infrastructure that dispatches your commands and uses a factory of some kind to locate the dependencies used by the created objects. 您应该创建某种基础结构来调度命令并使用某种工厂来查找创建的对象使用的依赖项。

In this way, the rest of your code doesn't have dependency resolution, and you are following a DI pattern except at the construction point. 这样,代码的其余部分没有依赖项解析,并且除了在构造点之外,您正在遵循DI模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM