gpt4 book ai didi

c# - Seemann 依赖注入(inject), "Three Calls Pattern"与服务定位器反模式

转载 作者:行者123 更新时间:2023-11-30 16:04:00 25 4
gpt4 key购买 nike

我使用依赖注入(inject) (DI) 和 Ninject 作为 DI 容器创建了一个 WinForms MVC 应用程序。基本架构如下

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

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

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(应用程序的主要形式)

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; }
}
}

在哪里

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

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

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")
};
}
}
}

实现的接口(interface)在哪里

public interface IDocumentController
{
bool Handles(string path);

void SetViewActive(bool isActive);

IDocumentView New(string fileName);

IDocumentView Open(string path);

IEnumerable<DocumentFileType> FileTypes { get; }
}

现在与这个 Controller 关联的 View 是

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; }
}
...
}

最后是 View 界面

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

public interface IDocumentView
{
void SetController(IDocumentController controller);

string DisplayName { get; set; }

bool StatusBarVisible { get; set; }
}

现在是我的问题。在 Seemann 的书“.NET 中的依赖注入(inject)”中,他谈到了“三次调用模式”,这就是我在上面试图实现的。代码有效,shell View 显示并通过 MVC 模式我的 Controller 正确打开 View 等。但是,我很困惑,因为上面肯定有“服务定位器反模式”的味道。在 Seemann 的书的第 3 章中,他说

The COMPOSITION ROOT pattern describes where you should use a DI CONTAINER. 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.

In its pure form, the REGISTER RESOLVE RELEASE pattern states that you should only make a single method call in each phase. Krzysztof Kozimic calls this the Three Calls Pattern.

Configuring a DI CONTAINER in a single method call requires more explanation. 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. Once configuration is completed, the container should be regarded as read-only.

这听起来像是被挖出的“服务定位器”,为什么这不被视为服务位置?


为了调整我的代码以使用 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>());
}

使用 Ninject.Extensions.Conventions ,然后我更改了ApplicationShellController为了更正我的代码以注入(inject) IDocumentController s 通过 ctor 注入(inject):

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");
}
...
}

在哪里

public class SpreadsheetController : IDocumentController 
{
private ISpreadsheetView view;

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

但这会导致循环依赖,我该如何处理呢?

问题总结:

  1. 为什么我最初使用 Ninject 使用“Thee Calls Pattern”和 CompositionRoot.Resolve<T>()服务定位器反模式不好或不同?
  2. 如果我想切换到纯 ctor 注入(inject),如何解决上述循环依赖问题?

非常感谢您的宝贵时间。

最佳答案

在流程的某个时刻,您必须使用服务位置。然而,DI 和 SL 之间的区别在于,在 SL 中,您在请求它们时解析您的服务,而在 DI 中,您在某种工厂(例如 Controller 工厂)中解析它们,然后构造您的对象并通过中的引用。

您应该创建某种基础设施来分派(dispatch)您的命令并使用某种工厂来定位所创建对象所使用的依赖项。

通过这种方式,您的其余代码没有依赖项解析,并且除了构造点外,您都遵循 DI 模式。

关于c# - Seemann 依赖注入(inject), "Three Calls Pattern"与服务定位器反模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35895538/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com