从服务定位器转向依赖注入
Moving from Service Locator to Dependency Injection
我准备了示例应用程序,我想在此基础上讨论转向依赖注入而不是服务定位器。我是 DI 的新手,所以请耐心等待。示例应用程序是使用 Simple Injector 作为 DI 库编写的。 Bootstrapper 中的注册按预期工作。每次我需要它时,我都有 IMessageBox 接口和新实例 ComputationCores 的单例。
我读了一些关于 DI 的文章,所以我知道应该有一些组合根以及它应该如何工作。但我发现只是非常基本的例子,没有真正的复杂性。
示例代码:
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 root 中调用 Container.GetInstance(解析方法),其他任何地方都可以。大多数依赖项应该在构造函数中注入。
Q1:
如果我转向 DI,我想 ManhattanProject 的构造函数应该看起来像这样:
ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca,IComputationCoreBeta ccb)。但这会导致每个 mb、cca、ccb 有一个实例。而不是每次 cca、ccb 的新实例都满足我的要求。
Q1a:
我想这可以通过 cca、ccb 的某种抽象工厂来解决,它可以根据请求给我新的实例。但是那么 - BootStrapper 的目的是什么?
Q2:
ManhattanProject 可以由使用不同 coputationCores 的更多 ProjectSubParts 组成 - 例如42. 因此,以这种方式使用构造函数注入(提供计算核心)是完全不合适的,应该使用某种外观。由于 facade 在构造函数中的 args 数量也应该有限,所以我最终会得到很多嵌套的 facades。我想这是错误的。
Q3:
我正在使用 ProjectSubParts,它允许我做一些工作。全部继承自 IProjectSubPart 接口。如果我想为不同的 ProjectSubParts 注入不同的实现,我应该怎么做?我是否应该为每个 ProjectSubpart 创建新接口以允许 DI 容器解析使用哪个实现?
Q4:
基于提供的示例(和服务定位器模式),我很容易创建 IComputationCoreAlpha 的实例,它可以在内部创建
每次我需要时,通过调用 DependencyResolver.GetInstance 来清理新的内部对象。此外,我可以完全控制它们,并且可以在使用完它们后调用 Dispose。如果在 DI 概念整个图中
将在 CompositionRoot 中创建,这种用法怎么可能?
谢谢
Q1: If I would move to DI I suppose that constructor of
ManhattanProject should looks something like this:
ManhattanProject(IMessageBox mb, IComputationCoreAlpha cca,
IComputationCoreBeta ccb).
类 应该只依赖于他们直接需要的服务。所以 ManhattanProject
不应该依赖于任何计算核心,而只是依赖于 IProjectSubPart
抽象。
Q1a: I suppose this could be solved by some kind of abstract factory
for cca, ccb which could give me new instance per request. But then -
what is the purpose of BootStrapper?
引导程序/组合根的目的是构建对象图。如果您创建一个工厂抽象,它需要在某个地方实现。这个 'somewhere' 是你的组合根。工厂实现应该在你的组合根目录中。
除了使用工厂,更好的方法是注入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)
});
通常,您甚至会屏蔽应用程序的其他部分,使其不必知道某个抽象有多个实现,但在您的情况下隐藏它似乎是不可能的,因为它是 ManhattanProject
即(当前)负责为每个 IProjectSubPart
提供不同的值。但是,如果可能的话,正确的解决方案是让 ManhattanProject
直接依赖于 IProjectSubPart
而不是依赖于 IEnumerable<IProjectSubPart>
并且您将让 Composition Root 注入一个复合实现,它将按照 here.
所述包装 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
实现可以注册为集合,如下所示:
container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
Q2: Since facade should have limited count of args in constructor
too I would end up with many and many nested facades.
很难说这有什么用。可能我给定的 LINQ 查询实现不适用于您的情况,但您的示例过于广泛,无法对此进行具体说明。最后你可能想要一个自定义抽象,但我还不确定。
Q3: I am using ProjectSubParts which allows me to do some work.
All inherits from IProjectSubPart interface. In case I would want
to inject different implementations for different ProjectSubParts
how should I do that? Should I create new interface for each
ProjectSubpart to allow DI container to resolve which
implementation use?
这在很大程度上取决于您的设计。为此,我们应该看一下 Liskov Substitution Principle,它基本上表示给定抽象的任何子类型都应该以与抽象兼容的方式运行。因此,在您的情况下,如果某个 class 期望某个 IProjectSubPart
实现,并且在不同的实现下无法正常运行,这意味着您违反了 Liskov 替换原则。这意味着这些实现的行为并不相同,即使它们可能具有完全相同的方法签名。在那种情况下,您应该将它们分成多个接口。
如果消费者仍然正常运行并且更改实现只是为了方便,那么让他们具有相同的抽象是可以的。一个很好的例子是带有 FileLogger
和 MailLogger
实现的 ILogger
抽象。在系统的某些部分,您可能认为通过邮件发送消息很重要。还是对于依赖ILogger的class,无论消息是写入文件,通过邮件发送,还是根本不发送,它的功能都是一样的。
是否违反 LSK 由您自行判断。
Q4: Based on provided sample (and Service Locator pattern) it
would be very easy for me to create instance of
IComputationCoreAlpha which could internally create new and
clean inner object by calling DependencyResolver.GetInstance
everytime I need. Moreover I would have full control over
them and I could call Dispose on them when I would be done
with their usage. If in DI concept whole graph would be
created in CompositionRoot how would be this kind of usage
possible?
我会说 DI 实际上让这项工作变得更容易。例如,让我们尝试使用服务定位器实现您想要的:
public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
{
public int RunComputation(int myParam) {
var heavyWeight = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return heavyWeight.RunComputation(myParam);
}
}
这是一个代理 class,它能够在调用 RunComputation
时延迟创建真实实例。但这实际上给我们提出了一个问题。如果我们看看消费者将如何使用它,这一点就会变得很清楚:
public int DoCalculations(int myParam) {
var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return ccA.RunComputation(myParam);
}
此处 DoCalculations
方法从服务定位器解析 IComputationCoreAlpha
。这个 return 为我们提供了 LazyComputationCoreAlphaProxy
实例(因为我们在定位器中注册了它)。解决后,我们将对其调用 RunComputation
。但在 RunComputation
中,我们再次解析 IComputationCoreAlpha
。我们想解决 IComputationCoreAlpha
,因为否则我们的 LazyComputationCoreAlphaProxy
需要直接依赖于不同的实现,但这会导致违反依赖倒置原则,并且可能会导致我们有许多不同的 LazyComputationCoreAlphaProxy
s。每个实现一个。
但是如果我们尝试在这里解析IComputationCoreAlpha
,定位器将return再次使用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);
}
}
这里我们将 Func 工厂注入到 LazyComputationCoreAlphaProxy
s 构造函数中。这允许代理忽略它创建的实际类型,同时仍然允许与以前相同的惰性行为。现在我们再次将构建对象图的那部分的责任委托给我们的组合根。我们可以如下手动连接:
LazyComputationCoreAlphaProxy(() => new SyncComputationCoreAlpha())
或者我们可以使用 Simple Injector 的装饰工具为我们做这个:
container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
container.RegisterDecorator(
typeof(IComputationCoreAlpha),
typeof(LazyComputationCoreAlphaProxy));
通过 RegisterDecorator
注册,Simple Injector 会自动将任何 IComputationCoreAlpha
实现包装在 LazyComputationCoreAlphaProxy
装饰器中。开箱即用,Simple Injector 理解装饰器内部的 Func 工厂委托,它将确保注入创建装饰对象的工厂。
但是因为我们现在讨论的是装饰器。装饰器的依赖注入为我们提供了更多改进代码的可能性。例如,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
,这是很多重复的代码,不仅使实际实现复杂化,而且还需要维护。什么可以将基础设施关注点(或横切关注点)从那些 classes 中移出,并只实现一次:在装饰器中:
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
不再需要依赖 IMessageBox
。这清理了实现(并且不要忘记您拥有的 42 个其他实现)。同样,如果我们手动创建这样的部分,我们将按如下方式进行:
new MessageBoxLoggingProjectSubPart(new ProjectSubPart1(computers));
然而,使用 Simple Injector,这变得更加容易:
container.RegisterCollection<IProjectSubPart>(new[] {
typeof(ProjectSubPart1),
typeof(ProjectSubPart2),
typeof(ProjectSubPartN)
});
container.RegisterDecorator(
typeof(IProjectSubPart),
typeof(MessageBoxLoggingProjectSubPart));
现在任何时候你想改变记录方式,你只需要改变MessageBoxLoggingProjectSubPart
。例如,当你想在操作完成后记录日志,或者在抛出异常的情况下。这使您不必在整个应用程序中进行彻底的更改(这就是 Open/closed Principle 的全部内容)。
抱歉拖了这么久post。这里有一些注入的土豆:
我准备了示例应用程序,我想在此基础上讨论转向依赖注入而不是服务定位器。我是 DI 的新手,所以请耐心等待。示例应用程序是使用 Simple Injector 作为 DI 库编写的。 Bootstrapper 中的注册按预期工作。每次我需要它时,我都有 IMessageBox 接口和新实例 ComputationCores 的单例。
我读了一些关于 DI 的文章,所以我知道应该有一些组合根以及它应该如何工作。但我发现只是非常基本的例子,没有真正的复杂性。
示例代码:
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 root 中调用 Container.GetInstance(解析方法),其他任何地方都可以。大多数依赖项应该在构造函数中注入。
Q1: 如果我转向 DI,我想 ManhattanProject 的构造函数应该看起来像这样: ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca,IComputationCoreBeta ccb)。但这会导致每个 mb、cca、ccb 有一个实例。而不是每次 cca、ccb 的新实例都满足我的要求。
Q1a: 我想这可以通过 cca、ccb 的某种抽象工厂来解决,它可以根据请求给我新的实例。但是那么 - BootStrapper 的目的是什么?
Q2: ManhattanProject 可以由使用不同 coputationCores 的更多 ProjectSubParts 组成 - 例如42. 因此,以这种方式使用构造函数注入(提供计算核心)是完全不合适的,应该使用某种外观。由于 facade 在构造函数中的 args 数量也应该有限,所以我最终会得到很多嵌套的 facades。我想这是错误的。
Q3: 我正在使用 ProjectSubParts,它允许我做一些工作。全部继承自 IProjectSubPart 接口。如果我想为不同的 ProjectSubParts 注入不同的实现,我应该怎么做?我是否应该为每个 ProjectSubpart 创建新接口以允许 DI 容器解析使用哪个实现?
Q4: 基于提供的示例(和服务定位器模式),我很容易创建 IComputationCoreAlpha 的实例,它可以在内部创建 每次我需要时,通过调用 DependencyResolver.GetInstance 来清理新的内部对象。此外,我可以完全控制它们,并且可以在使用完它们后调用 Dispose。如果在 DI 概念整个图中 将在 CompositionRoot 中创建,这种用法怎么可能?
谢谢
Q1: If I would move to DI I suppose that constructor of ManhattanProject should looks something like this: ManhattanProject(IMessageBox mb, IComputationCoreAlpha cca, IComputationCoreBeta ccb).
类 应该只依赖于他们直接需要的服务。所以 ManhattanProject
不应该依赖于任何计算核心,而只是依赖于 IProjectSubPart
抽象。
Q1a: I suppose this could be solved by some kind of abstract factory for cca, ccb which could give me new instance per request. But then - what is the purpose of BootStrapper?
引导程序/组合根的目的是构建对象图。如果您创建一个工厂抽象,它需要在某个地方实现。这个 'somewhere' 是你的组合根。工厂实现应该在你的组合根目录中。
除了使用工厂,更好的方法是注入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)
});
通常,您甚至会屏蔽应用程序的其他部分,使其不必知道某个抽象有多个实现,但在您的情况下隐藏它似乎是不可能的,因为它是 ManhattanProject
即(当前)负责为每个 IProjectSubPart
提供不同的值。但是,如果可能的话,正确的解决方案是让 ManhattanProject
直接依赖于 IProjectSubPart
而不是依赖于 IEnumerable<IProjectSubPart>
并且您将让 Composition Root 注入一个复合实现,它将按照 here.
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
实现可以注册为集合,如下所示:
container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
Q2: Since facade should have limited count of args in constructor too I would end up with many and many nested facades.
很难说这有什么用。可能我给定的 LINQ 查询实现不适用于您的情况,但您的示例过于广泛,无法对此进行具体说明。最后你可能想要一个自定义抽象,但我还不确定。
Q3: I am using ProjectSubParts which allows me to do some work. All inherits from IProjectSubPart interface. In case I would want to inject different implementations for different ProjectSubParts how should I do that? Should I create new interface for each ProjectSubpart to allow DI container to resolve which implementation use?
这在很大程度上取决于您的设计。为此,我们应该看一下 Liskov Substitution Principle,它基本上表示给定抽象的任何子类型都应该以与抽象兼容的方式运行。因此,在您的情况下,如果某个 class 期望某个 IProjectSubPart
实现,并且在不同的实现下无法正常运行,这意味着您违反了 Liskov 替换原则。这意味着这些实现的行为并不相同,即使它们可能具有完全相同的方法签名。在那种情况下,您应该将它们分成多个接口。
如果消费者仍然正常运行并且更改实现只是为了方便,那么让他们具有相同的抽象是可以的。一个很好的例子是带有 FileLogger
和 MailLogger
实现的 ILogger
抽象。在系统的某些部分,您可能认为通过邮件发送消息很重要。还是对于依赖ILogger的class,无论消息是写入文件,通过邮件发送,还是根本不发送,它的功能都是一样的。
是否违反 LSK 由您自行判断。
Q4: Based on provided sample (and Service Locator pattern) it would be very easy for me to create instance of IComputationCoreAlpha which could internally create new and clean inner object by calling DependencyResolver.GetInstance everytime I need. Moreover I would have full control over them and I could call Dispose on them when I would be done with their usage. If in DI concept whole graph would be created in CompositionRoot how would be this kind of usage possible?
我会说 DI 实际上让这项工作变得更容易。例如,让我们尝试使用服务定位器实现您想要的:
public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
{
public int RunComputation(int myParam) {
var heavyWeight = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return heavyWeight.RunComputation(myParam);
}
}
这是一个代理 class,它能够在调用 RunComputation
时延迟创建真实实例。但这实际上给我们提出了一个问题。如果我们看看消费者将如何使用它,这一点就会变得很清楚:
public int DoCalculations(int myParam) {
var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
return ccA.RunComputation(myParam);
}
此处 DoCalculations
方法从服务定位器解析 IComputationCoreAlpha
。这个 return 为我们提供了 LazyComputationCoreAlphaProxy
实例(因为我们在定位器中注册了它)。解决后,我们将对其调用 RunComputation
。但在 RunComputation
中,我们再次解析 IComputationCoreAlpha
。我们想解决 IComputationCoreAlpha
,因为否则我们的 LazyComputationCoreAlphaProxy
需要直接依赖于不同的实现,但这会导致违反依赖倒置原则,并且可能会导致我们有许多不同的 LazyComputationCoreAlphaProxy
s。每个实现一个。
但是如果我们尝试在这里解析IComputationCoreAlpha
,定位器将return再次使用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);
}
}
这里我们将 Func 工厂注入到 LazyComputationCoreAlphaProxy
s 构造函数中。这允许代理忽略它创建的实际类型,同时仍然允许与以前相同的惰性行为。现在我们再次将构建对象图的那部分的责任委托给我们的组合根。我们可以如下手动连接:
LazyComputationCoreAlphaProxy(() => new SyncComputationCoreAlpha())
或者我们可以使用 Simple Injector 的装饰工具为我们做这个:
container.RegisterCollection<IComputationCoreAlpha>(new[] {
typeof(SyncComputationCoreAlpha),
typeof(AsyncComputationCoreAlpha)
});
container.RegisterDecorator(
typeof(IComputationCoreAlpha),
typeof(LazyComputationCoreAlphaProxy));
通过 RegisterDecorator
注册,Simple Injector 会自动将任何 IComputationCoreAlpha
实现包装在 LazyComputationCoreAlphaProxy
装饰器中。开箱即用,Simple Injector 理解装饰器内部的 Func 工厂委托,它将确保注入创建装饰对象的工厂。
但是因为我们现在讨论的是装饰器。装饰器的依赖注入为我们提供了更多改进代码的可能性。例如,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
,这是很多重复的代码,不仅使实际实现复杂化,而且还需要维护。什么可以将基础设施关注点(或横切关注点)从那些 classes 中移出,并只实现一次:在装饰器中:
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
不再需要依赖 IMessageBox
。这清理了实现(并且不要忘记您拥有的 42 个其他实现)。同样,如果我们手动创建这样的部分,我们将按如下方式进行:
new MessageBoxLoggingProjectSubPart(new ProjectSubPart1(computers));
然而,使用 Simple Injector,这变得更加容易:
container.RegisterCollection<IProjectSubPart>(new[] {
typeof(ProjectSubPart1),
typeof(ProjectSubPart2),
typeof(ProjectSubPartN)
});
container.RegisterDecorator(
typeof(IProjectSubPart),
typeof(MessageBoxLoggingProjectSubPart));
现在任何时候你想改变记录方式,你只需要改变MessageBoxLoggingProjectSubPart
。例如,当你想在操作完成后记录日志,或者在抛出异常的情况下。这使您不必在整个应用程序中进行彻底的更改(这就是 Open/closed Principle 的全部内容)。
抱歉拖了这么久post。这里有一些注入的土豆: