yad*_*adu 3 c# dependency-injection simple-injector
在我的应用程序中,我想使用我的DI容器Simple Injector构造以下对象图:
new Mode1(
new CommonBuilder(
new Grouping(
new GroupingStrategy1())), // NOTE: Strategy 1
new CustomBuilder());
new Mode2(
new CommonBuilder(
new Grouping(
new GroupingStrategy2())), // NOTE: Strategy 2
new CustomBuilder());
Run Code Online (Sandbox Code Playgroud)
以下类代表上述图形:
public class Mode1 : IMode
{
private readonly ICommonBuilder commonBuilder;
private readonly ICustomBuilder customBuilder;
public Mode1(ICommonBuilder commonBuilder, ICustomBuilder ICustomBuilder customBuilder)
{
this.commonBuilder = commonBuilder;
this.customBuilder = customBuilder;
}
public void Run()
{
this.commonBuilder.Build();
this.customBuilder.Build();
//some code specific to Mode1
}
}
public class Mode2 : IMode
{
//same code as in Mode1
public void Run()
{
this.commonBuilder.Build();
this.customBuilder.Build();
//some code specific to Mode2
}
}
Run Code Online (Sandbox Code Playgroud)
随着CommonBuilder
和Grouping
为:
public class CommonBuilder : ICommonBuilder
{
private readonly IGrouping grouping;
public CommonBuilder(IGrouping grouping)
{
this.grouping = grouping;
}
public void Build()
{
this.grouping.Group();
}
}
public class Grouping : IGrouping
{
//Grouping strategy should be binded based on mode it is running
private readonly IGroupingStrategy groupingStrategy;
public Grouping(IGroupingStrategy groupingStrategy)
{
this.groupingStrategy = groupingStrategy;
}
public void Group()
{
this.groupingStrategy.Execute();
}
}
Run Code Online (Sandbox Code Playgroud)
我在项目中使用用于DI的Simple Injector。如上所示,我已经按照用户偏好选择了两种代码模式,每种模式都有通用代码(我不想重复),我想绑定分组策略( ve根据执行模式在我的通用代码中采用2种分组策略,每种模式一种。我遇到了使用工厂并在运行时在绑定之间进行切换的解决方案,但是我不想使用该解决方案,因为我在代码的多个位置都有相同的场景(我将最终创建多个工厂) 。
任何人都可以建议如何以更清洁的方式进行绑定
您可以使用基于上下文的注入。但是,由于基于依赖项的使用者(或其父母的父母)的使用者进行的基于上下文的注入会导致各种复杂性和细微的错误(尤其是在缓存诸如Scoped
和Singleton
涉及的生活方式时),因此简单注入器的API将您限制为向上看一级。
有几种方法可以解决Simple Injector中的这种看似限制。您应该做的第一件事是退后一步,看看您是否可以简化设计,因为这些要求经常(但并非总是)来自设计效率低下。这样的问题之一就是违反了Liskov替代原则(LSP)。从这个角度来看,最好问自己一个问题,Mode1
当它注入Grouping
包含策略的a时会中断Mode2
吗?如果答案是肯定的,则可能是您违反了LSP,并且首先应该首先尝试解决该问题。修复后,您可能还会看到配置问题也消失了。
如果确定设计不违反LSP,则第二好的选择是将有关消费者消费者的类型信息直接刻录到图形中。这是一个简单的示例:
var container = new Container();
container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();
container.RegisterConditional(
typeof(ICommonBuilder),
c => typeof(CommonBuilder<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Transient,
c => true);
container.RegisterConditional(
typeof(IGrouping),
c => typeof(Grouping<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Transient,
c => true);
container.RegisterConditional<IGroupingStrategy, Strategy1>(
c => typeof(Model1) == c.Consumer.ImplementationType
.GetGenericArguments().Single() // Consumer.Consumer
.GetGenericArguments().Single(); // Consumer.Consumer.Consumer
container.RegisterConditional<IGroupingStrategy, Strategy2>(
c => typeof(Mode2)) == c.Consumer.ImplementationType
.GetGenericArguments().Single()
.GetGenericArguments().Single();
Run Code Online (Sandbox Code Playgroud)
在此示例中,代替使用非泛型Grouping
类,将Grouping<T>
创建一个新类,并对进行相同操作CommonBuilder<T>
。这些类可以是非泛型Grouping
和CommonBuilder
类的子类,放置在您的“合成根”中,因此您不必为此更改应用程序代码:
class Grouping<T> : Grouping // inherit from base class
{
public Grouping(IGroupingStrategy strategy) : base(strategy) { }
}
class CommonBuilder<T> : CommonBuilder // inherit from base class
{
public CommonBuilder(IGrouping grouping) : base(grouping) { }
}
Run Code Online (Sandbox Code Playgroud)
使用此通用名称,CommonBuilder<T>
您可以进行注册,T
成为的类型将成为注入的使用者。换句话说,Mode1
将注入CommonBuilder<Mode1>
并Mode2
得到CommonBuilder<Mode2>
。这与在文档中注册ILogger
实现时相同。但是,由于具有通用类型,因此将注入。CommonBuilder<Mode1>
Grouping<CommonBuilder<Mode1>>
这些注册并不是真正有条件的,而是上下文相关的。注入的类型根据其使用者而变化。但是,此构造使IGrouping
消费者的类型信息在构造的对象图中可用。这允许IGroupingStrategy
基于该类型信息来应用条件注册。这是在注册谓词内部发生的事情:
c => typeof(Mode2)) == c.Consumer.ImplementationType // = Grouping<CommonBuilder<Mode2>>
.GetGenericArguments().Single() // = CommonBuilder<Mode2>
.GetGenericArguments().Single(); // = Mode2
Run Code Online (Sandbox Code Playgroud)
换句话说,如果我们可以通过以下方式更改IGrouping
实现,即实现的类型(Grouping<T>
)提供有关其使用者(IMode
实现)的信息。这样,的条件注册IGroupingStrategy
可以使用有关其消费者的消费者的信息。
在这里,注册请求使用者的实现类型(将为Grouping<Mode1>
或Grouping<Mode2>
),并将从该实现中获取单个通用参数(将为Mode1
或Mode2
)。换句话说,这使我们能够吸引消费者的消费者。可以将其与预期类型匹配以返回true
或false
。
尽管这看起来有些尴尬和复杂,但是此模型的优点是Simple Injector知道完整的对象图,从而可以分析和验证对象图。它还允许进行自动接线。换句话说,如果IGrouping
或IGroupingStrategy
实现具有(其他)依赖项,则Simple Injector将自动注入它们并验证其正确性。它还允许您可视化对象图而不会丢失任何信息。例如,如果将鼠标悬停在Visual Studio调试器中,则这是Simple Injector将显示的图形:
Mode1(
CommonBuilder<Mode1>(
Grouping<CommonBuilder<Mode1>>(
Strategy1()))
Run Code Online (Sandbox Code Playgroud)
这种方法的明显缺点是,如果将CommonBuilder<T>
或Grouping<T>
注册为单例,则每个封闭泛型类型现在将有一个实例。这意味着CommonBuilder<Mode1>
它将与实例不同CommonBuilder<Mode2>
。
另外,您也可以CommonBuilder
按以下条件设置注册条件:
var container = new Container();
container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();
container.RegisterConditional<ICommonBuilder>(
Lifestyle.Transient.CreateRegistration(
() => new CommonBuilder(new Grouping(new Strategy1())),
container),
c => c.Consumer.ImplementationType == typeof(Mode1));
container.RegisterConditional<ICommonBuilder>(
Lifestyle.Transient.CreateRegistration(
() => new CommonBuilder(new Grouping(new Strategy2())),
container),
c => c.Consumer.ImplementationType == typeof(Mode2));
Run Code Online (Sandbox Code Playgroud)
这比以前的方法要简单一些,但是它禁用了自动装配。在这种情况下CommonBuilder
,其依赖关系是手工连接的。当对象图很简单(不包含很多依赖项)时,此方法就足够了。当依赖被添加到任CommonBuilder
,Grouping
或策略,不过,这可能会导致高额的维护和可能会隐藏缺陷的简单喷油器将无法验证代表您的依赖关系图。
请注意有关RegisterConditional
方法的文档中的以下声明:
谓词仅在对象图编译期间使用,并且谓词的结果将在返回的对象图的结构中燃烧。对于请求的类型,将在每个后续调用中创建完全相同的图。这不允许基于运行时条件更改图形。
归档时间: |
|
查看次数: |
155 次 |
最近记录: |