我准备了基于此的示例应用程序,我想讨论转移到Dependency Injection而不是Service Locator。我是DI的新手,所以请耐心等待我。示例应用程序使用Simple Injector作为DI库编写。 Bootstrapper中的注册按预期工作。我每次需要时都有IMessageBox接口的单例和新的实例ComputationCores。
我读了一些关于DI的文章,所以我知道应该有一些Composition根以及它应该如何工作。但我找到了非常基本的例子而没有真正的单词复杂性。
示例代码:
public class DependencyResolver
{
public static Func<Type, object> ResolveMe;
public static T GetInstance<T>() where T : class
{
return (T)ResolveMe(typeof (T));
}
}
public interface IMessageBox
{
void ShowMessage(string message);
}
public class StandardMessageBox : IMessageBox
{
public StandardMessageBox()
{
Console.WriteLine("StandardMessageBox constructor called...");
}
~StandardMessageBox()
{
Console.WriteLine("StandardMessageBox destructor called...");
}
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
public interface IComputationCoreAlpha
{
int RunComputation(int myParam);
}
public class SyncComputationCoreAlpha : IComputationCoreAlpha
{
public SyncComputationCoreAlpha()
{
Console.WriteLine("SyncComputationCoreAlpha constructor called...");
}
~SyncComputationCoreAlpha()
{
Console.WriteLine("SyncComputationCoreAlpha destructor called...");
}
public int RunComputation(int myParam)
{
return myParam * myParam;
}
}
public class AsyncComputationCoreAlpha : IComputationCoreAlpha
{
public AsyncComputationCoreAlpha()
{
Console.WriteLine("AsyncComputationCoreAlpha constructor called...");
}
~AsyncComputationCoreAlpha()
{
Console.WriteLine("AsyncComputationCoreAlpha destructor called...");
}
public int RunComputation(int myParam)
{
return myParam * myParam;
}
}
public interface IComputationCoreBeta
{
int RunComputation(int myParam);
}
public class SyncComputationCoreBeta : IComputationCoreBeta
{
public SyncComputationCoreBeta()
{
Console.WriteLine("SyncComputationCoreBeta constructor called...");
}
~SyncComputationCoreBeta()
{
Console.WriteLine("SyncComputationCoreBeta destructor called...");
}
public int RunComputation(int myParam)
{
return myParam * myParam;
}
}
public class AsyncComputationCoreBeta : IComputationCoreBeta
{
public AsyncComputationCoreBeta()
{
Console.WriteLine("AsyncComputationCoreBeta constructor called...");
}
~AsyncComputationCoreBeta()
{
Console.WriteLine("AsyncComputationCoreBeta destructor called...");
}
public int RunComputation(int myParam)
{
return myParam * myParam;
}
}
public interface IProjectSubPart
{
int DoCalculations(int myParam);
}
public class ProjectSubPart1 : IProjectSubPart
{
public int DoCalculations(int myParam)
{
var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
messageBoxService.ShowMessage("Hardly working 1...");
var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
var ccB = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return ccA.RunComputation(myParam) + ccB.RunComputation(myParam + 1);
}
}
public class ProjectSubPart2 : IProjectSubPart
{
public int DoCalculations(int myParam)
{
var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
messageBoxService.ShowMessage("Hardly working 2...");
var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return ccA.RunComputation(myParam * 3);
}
}
public class ProjectSubPartN : IProjectSubPart
{
public int DoCalculations(int myParam)
{
var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
messageBoxService.ShowMessage("Hardly working N...");
return -3;
}
}
public class ManhattanProject
{
public void RunProject()
{
var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
messageBoxService.ShowMessage("Project started...");
var subPart1 = new ProjectSubPart1();
var subPart2 = new ProjectSubPart2();
var subPartN = new ProjectSubPartN();
var result = subPart1.DoCalculations(1) + subPart2.DoCalculations(2) + subPartN.DoCalculations(3);
messageBoxService.ShowMessage(string.Format("Project finished with magic result {0}", result));
}
}
public class Sample
{
public void Run()
{
BootStrapper();
var mp = DependencyResolver.GetInstance<ManhattanProject>();
mp.RunProject();
}
private void BootStrapper()
{
var container = new Container();
container.RegisterSingle<IMessageBox, StandardMessageBox>();
container.Register<IComputationCoreAlpha, SyncComputationCoreAlpha>();
container.Register<IComputationCoreBeta, AsyncComputationCoreBeta>();
DependencyResolver.ResolveMe = container.GetInstance;
}
}
在DI中,只允许在Composition根中调用Container.GetInstance(resolve方法),而不是其他地方。应该在构造函数中注入大多数依赖项。
Q1: 如果我要转到DI,我认为ManhattanProject的构造函数看起来像这样: ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca,IComputationCoreBeta ccb)。但这会导致每mb,cca,ccb有一个实例。而不是每次cca的新实例,ccb对我的要求。
Q1A: 我想这可以通过cca,ccb的某种抽象工厂来解决,它可以为每个请求提供新的实例。但那么 - BootStrapper的目的是什么?
Q2: ManhattanProject可以包含更多使用不同coputationCores的ProjectSubParts - 例如42.因此,以这种方式使用构造函数注入(提供计算核心)并且应该使用某种外观是完全不合适的。由于外观应该在构造函数中具有有限的args数量,所以我最终会得到许多嵌套的外观。我想这是错的。
Q3: 我正在使用ProjectSubParts,它允许我做一些工作。所有都继承自IProjectSubPart interace。如果我想为不同的ProjectSubParts注入不同的实现,我该怎么做?我应该为每个ProjectSubpart创建新接口,以允许DI容器解析使用哪个实现?
Q4: 基于提供的示例(和服务定位器模式),我很容易创建IComputationCoreAlpha的实例,它可以在内部创建 每次我需要的时候通过调用DependencyResolver.GetInstance来获得新的和干净的内部对象。此外,我可以完全控制它们,当我完成它们的使用时,我可以调用Dispose。如果在DI概念全图中 将在CompositionRoot中创建如何使用这种用法?
由于
答案 0 :(得分:3)
Q1:如果我要转向DI,我认为是构造函数 ManhattanProject应该看起来像这样: ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca, IComputationCoreBeta ccb)。
类应该只依赖于他们需要的服务。所以ManhattanProject
不应该依赖于任何计算核心,而应该依赖于IProjectSubPart
抽象。
Q1a:我想这可以通过某种抽象工厂来解决 对于cca,ccb,它可以为每个请求提供新的实例。但是之后 - BootStrapper的目的是什么?
bootstrapper / composition root的目的是建立对象图。如果您创建工厂抽象,则需要在某处实现。这个&#39;某处&#39;是你的作文根。工厂实现应该在您的组合根目录中。
除了使用工厂外,更好的方法是注入IEnumerable<IProjectSubPart>
。在这种情况下,您的ManhattanProject
将如下所示:
public class ManhattanProject
{
private readonly IMessageBox messageBoxService;
private readonly IEnumerable<IProjectSubPart> parts;
public ManhattanProject(IMessageBox messageBoxService,
IEnumerable<IProjectSubPart> parts) {
this.messageBoxService = messageBoxService;
this.parts = parts;
}
public void RunProject() {
messageBoxService.ShowMessage("Project started...");
var calculationResults =
from pair in parts.Select((part, index) => new { part, value = index + 1 })
select pair.part.DoCalculations(pair.value);
var result = calculationResults.Sum();
messageBoxService.ShowMessage(
string.Format("Project finished with magic result {0}", result));
}
}
当您依赖IEnumerable<IProjectSubPart>
时,每次向系统添加新的ManhattanProject
实施时,都可以阻止IProjectSubPart
更改。在Simple Injector中,您可以按如下方式注册:
// Simple Injector v3.x
container.RegisterSingleton<IMessageBox, StandardMessageBox>();
container.Register<ManhattanProject>();
container.RegisterCollection<IProjectSubPart>(new[] {
typeof(ProjectSubPart1),
typeof(ProjectSubPart2),
typeof(ProjectSubPartN)
});
// Simple Injector v2.x
container.RegisterSingle<IMessageBox, StandardMessageBox>();
container.Register<ManhattanProject>();
container.RegisterAll<IProjectSubPart>(new[] {
typeof(ProjectSubPart1),
typeof(ProjectSubPart2),
typeof(ProjectSubPartN)
});
一般来说,你甚至可以保护应用程序的其他部分不必知道某个抽象有多个实现,但是在你的情况下隐藏它似乎不可能,因为它是{{1} (当前)负责为每个ManhattanProject
提供不同的值。但是,如果可能的话,正确的解决方案是让IProjectSubPart
直接依赖ManhattanProject
而不是依赖IProjectSubPart
,你会让组合根注入一个复合实现,如here所描述的那样包裹IEnumerable<IProjectSubPart>
。
相同的模式可以应用于所有IEnumerable<IProjectSubPart>
实现。例如:
IProjectSubPart
这些public class ProjectSubPart1 : IProjectSubPart
{
private readonly IMessageBox messageBoxService;
private readonly IEnumerable<IComputationCoreAlpha> computers;
public ProjectSubPart1(IMessageBox messageBoxService,
IEnumerable<IComputationCoreAlpha> computers) {
this.messageBoxService = messageBoxService;
this.computers = computers;
}
public int DoCalculations(int myParam) {
messageBoxService.ShowMessage("Hardly working 1...");
var calculationResults =
from pair in computers.Select((computer, index) => new { computer, index })
select pair.computer.RunComputation(myParam + pair.index);
return calculationResults.Sum();
}
}
实现可以按如下方式注册为集合:
IComputationCoreAlpha
Q2:因为在构造函数中,facade应该具有有限的args数量 我最终会得到很多很多嵌套的外墙。
很难说对此有用。可能我使用LINQ查询的给定实现在你的情况下不起作用,但你的例子太广泛而不是非常具体。最后你可能想要一个自定义抽象,但我还不确定。
问题3:我正在使用ProjectSubParts,它允许我做一些工作。 所有都继承自IProjectSubPart接口。如果我想要的话 为不同的ProjectSubParts注入不同的实现 我应该怎么做?我应该为每个人创建新的界面吗? ProjectSubpart允许DI容器解析哪个 实施使用?
这在很大程度上取决于您的设计。我们应该看一下Liskov Substitution Principle,这基本上说给定抽象的任何子类型都应该以与抽象兼容的方式运行。因此,在您的情况下,如果某个类需要某个container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
实现,并且不能正确地使用不同的实现,则意味着您正在破坏Liskov替换原则。这意味着这些实现的行为不一样,即使它们可能具有确切的方法签名。在这种情况下,您应该将它们分成多个接口。
如果消费者仍然能够正常运行并且改变实现只是一些方便,那么让他们拥有相同的抽象是可以的。好的例子是IProjectSubPart
抽象,ILogger
和FileLogger
实现。在系统的某些部分,您可能认为通过邮件发送邮件很重要。对于依赖于ILogger的类,无论消息是写入文件,通过邮件发送还是根本不发送,它的功能都相同。
您是否违反了LSK,由您决定。
第四季度:基于提供的样本(和服务定位器模式) 对我来说很容易创建实例 IComputationCoreAlpha可以在内部创建新的和 通过调用DependencyResolver.GetInstance来清理内部对象 我每次都需要。而且我完全可以控制 当我完成时,他们可以打电话给他们 与他们的用法。如果在DI概念中整个图形将是 在CompositionRoot中创建的将是这种用法 可能的?
我会说DI实际上使这项工作更容易。例如,让我们尝试使用服务定位器实现您想要的内容:
MailLogger
这是一个代理类,能够在调用public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
{
public int RunComputation(int myParam) {
var heavyWeight = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return heavyWeight.RunComputation(myParam);
}
}
时懒洋洋地创建实例。但这实际上给我们带来了一个问题。如果我们看看消费者将如何使用它,这一点就变得清晰了:
RunComputation
此处public int DoCalculations(int myParam) {
var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return ccA.RunComputation(myParam);
}
方法解析服务定位器中的DoCalculations
。这将返回IComputationCoreAlpha
实例(因为我们在定位器中注册了该实例)。解决之后,我们会在其上调用LazyComputationCoreAlphaProxy
。但在RunComputation
内,我们再次解决了RunComputation
。我们想要解决IComputationCoreAlpha
,因为否则我们的IComputationCoreAlpha
需要直接依赖于不同的实现,但这会导致违反依赖性倒置原则,并且可能会导致我们有许多不同的{ {1}}秒。每个实现一个。
但是如果我们尝试在这里解析LazyComputationCoreAlphaProxy
,定位器将再次返回LazyComputationCoreAlphaProxy
,这最终会导致堆栈溢出异常。
现在让我们来看看依赖注入的外观:
IComputationCoreAlpha
这里我们将一个Func工厂注入LazyComputationCoreAlphaProxy
的构造函数中。这允许代理不知道它创建的实际类型,同时仍然允许与以前相同的惰性行为。现在我们将构建对象图的那一部分的责任再次委托给我们的组合根。我们可以按如下方式手动连接:
public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
{
private readonly Func<IComputationCoreAlpha> factory;
public LazyComputationCoreAlphaProxy(Func<IComputationCoreAlpha> factory) {
this.factory = factory;
}
public int RunComputation(int myParam) {
var heavyWeight = this.factory.Invoke();
return heavyWeight.RunComputation(myParam);
}
}
或者我们可以使用Simple Injector的装饰工具为我们做这件事:
LazyComputationCoreAlphaProxy
通过LazyComputationCoreAlphaProxy(() => new SyncComputationCoreAlpha())
注册,Simple Injector将自动将container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
container.RegisterDecorator(
typeof(IComputationCoreAlpha),
typeof(LazyComputationCoreAlphaProxy));
实现包装在RegisterDecorator
装饰器中。开箱即用,Simple Injector了解decotator内部的Func工厂代表,它将确保注入一个创建装饰对象的工厂。
但是因为我们现在正处理装饰者的问题。使用装饰器的依赖注入为我们提供了更多改进代码的可能性。例如,IComputationCoreAlpha
中的大部分代码看起来都很相似。它们都具有相同的消息框记录代码:
LazyComputationCoreAlphaProxy
如果你有许多不同的IProjectSubPart
,这是很多重复的代码,不仅使实际的实现复杂化,而且还需要维护。什么可以将这些基础设施问题(或跨领域关注点)从这些类中移出,并且只实现一次:在装饰者中:
public class ProjectSubPart1 : IProjectSubPart
{
private readonly IMessageBox messageBoxService;
private readonly IEnumerable<IComputationCoreAlpha> computers;
public ProjectSubPart1(IMessageBox messageBoxService,
IEnumerable<IComputationCoreAlpha> computers) {
this.messageBoxService = messageBoxService;
this.computers = computers;
}
public int DoCalculations(int myParam) {
messageBoxService.ShowMessage("Hardly working 1...");
// part specific calculation
}
}
使用此装饰器,您可以将部件简化为以下内容:
IProjectSubPart
请注意public class MessageBoxLoggingProjectSubPart : IProjectSubPart
{
private readonly IMessageBox messageBoxService;
private readonly IProjectSubPart decoratee;
public MessageBoxLoggingProjectSubPart(IMessageBox messageBoxService,
IProjectSubPart decoratee) {
this.messageBoxService = messageBoxService;
this.decoratee = decoratee;
}
public int DoCalculations(int myParam) {
messageBoxService.ShowMessage("Hardly working 1...");
return this.decoratee.DoCalculations(myParam);
}
}
不再需要public class ProjectSubPart1 : IProjectSubPart
{
private readonly IEnumerable<IComputationCoreAlpha> computers;
public ProjectSubPart1(IEnumerable<IComputationCoreAlpha> computers) {
this.computers = computers;
}
public int DoCalculations(int myParam) {
var calculationResults =
from pair in computers.Select((computer, index) => new { computer, index })
select pair.computer.RunComputation(myParam + pair.index);
return calculationResults.Sum();
}
}
依赖ProjectSubPart1
。这样可以清理实现(并且不要忘记您拥有的其他42个实现)。同样,如果我们手动创建这样的部分,我们将按如下方式进行:
IMessageBox
然而,使用Simple Injector,这变得更加容易:
new MessageBoxLoggingProjectSubPart(new ProjectSubPart1(computers));
现在,只要您想要更改记录事物的方式,您只需要更改container.RegisterCollection<IProjectSubPart>(new[] {
typeof(ProjectSubPart1),
typeof(ProjectSubPart2),
typeof(ProjectSubPartN)
});
container.RegisterDecorator(
typeof(IProjectSubPart),
typeof(MessageBoxLoggingProjectSubPart));
。例如,您希望在操作完成后记录,或者在抛出异常时。这可以防止您在整个应用程序中进行彻底的更改(正如Open/closed Principle所做的那样)。
对不起这篇长篇帖子我很抱歉。这里有一些注入的土豆: