关于这个话题有很多讨论,但每个人似乎都错过了一个明显的答案。我想帮助审查这个“明显的”IOC容器解决方案。各种对话假设运行时选择策略和使用IOC容器。我将继续这些假设。
我还想补充一个假设,即它不是必须选择的单一策略。相反,我可能需要检索一个对象图,它在图的每个节点中都有几个策略。
我将首先快速概述两个常用的解决方案,然后我将展示我希望看到IOC容器支持的“明显”替代方案。我将使用Unity作为示例语法,但我的问题不是Unity特有的。
这种方法要求每个新策略都手动添加绑定:
Container.RegisterType<IDataAccess, DefaultAccessor>();
Container.RegisterType<IDataAccess, AlphaAccessor>("Alpha");
Container.RegisterType<IDataAccess, BetaAccessor>("Beta");
...然后明确要求正确的策略:
var strategy = Container.Resolve<IDataAccess>("Alpha");
为了说明这种方法,假设以下类:
public class DataAccessFactory{
public IDataAccess Create(string strategy){
return //insert appropriate creation logic here.
}
public IDataAccess Create(){
return //Choose strategy through ambient context, such as thread-local-storage.
}
}
public class Consumer
{
public Consumer(DataAccessFactory datafactory)
{
//variation #1. Not sufficient to meet requirements.
var myDataStrategy = datafactory.Create("Alpha");
//variation #2. This is sufficient for requirements.
var myDataStrategy = datafactory.Create();
}
}
IOC容器具有以下绑定:
Container.RegisterType<DataAccessFactory>();
这是我想要使用的方法,而不是上述两种方法。它涉及提供委托作为IOC容器绑定的一部分。大多数IOC容器都具备此功能,但这种特定方法具有重要的细微差别。
语法如下:
Container.RegisterType(typeof(IDataAccess),
new InjectionStrategy((c) =>
{
//Access ambient context (perhaps thread-local-storage) to determine
//the type of the strategy...
Type selectedStrategy = ...;
return selectedStrategy;
})
);
请注意,InjectionStrategy
不返回IDataAccess
的实例。相反,它返回一个实现IDataAccess
的类型描述。然后,IOC容器将执行该类型的常规创建和“构建”,其中可能包括正在选择的其他策略。
这与标准的类型到委托的绑定形成对比,在Unity的情况下,它的编码如下:
Container.RegisterType(typeof(IDataAccess),
new InjectionFactory((c) =>
{
//Access ambient context (perhaps thread-local-storage) to determine
//the type of the strategy...
IDataAccess instanceOfSelectedStrategy = ...;
return instanceOfSelectedStrategy;
})
);
以上实际上接近满足整体需求,但绝对不符合假设的Unity InjectionStrategy
。
关注第一个样本(使用假设的Unity InjectionStrategy
):
Type
不可用,这意味着第一次返回该类型时可能会遇到微小的性能损失。换句话说,容器必须在现场反映类型以发现它具有的构造函数,以便它知道如何注入它。该类型的所有后续出现都应该很快,因为容器可以缓存第一次找到的结果。这不是一个值得一提的“骗局”,但我正在努力进行全面披露。是否存在可以这种方式运行的现有IOC容器?任何人都有Unity自定义注入类来实现这种效果?
答案 0 :(得分:14)
据我所知,这个问题是关于运行时选择或几个候选策略之一的映射。
没有理由依赖DI容器来执行此操作,因为至少有三种方法可以以容器无关的方式执行此操作:
我个人偏好是部分类型名称角色提示。
答案 1 :(得分:1)
这是一个迟到的回应,但也许会有所帮助。
我有一个非常简单的方法。我只是创建一个StrategyResolver而不是直接依赖Unity。
public class StrategyResolver : IStrategyResolver
{
private IUnityContainer container;
public StrategyResolver(IUnityContainer unityContainer)
{
this.container = unityContainer;
}
public T Resolve<T>(string namedStrategy)
{
return this.container.Resolve<T>(namedStrategy);
}
}
用法:
public class SomeClass: ISomeInterface
{
private IStrategyResolver strategyResolver;
public SomeClass(IStrategyResolver stratResolver)
{
this.strategyResolver = stratResolver;
}
public void Process(SomeDto dto)
{
IActionHandler actionHanlder = this.strategyResolver.Resolve<IActionHandler>(dto.SomeProperty);
actionHanlder.Handle(dto);
}
}
注册:
container.RegisterType<IActionHandler, ActionOne>("One");
container.RegisterType<IActionHandler, ActionTwo>("Two");
container.RegisterType<IStrategyResolver, StrategyResolver>();
container.RegisterType<ISomeInterface, SomeClass>();
现在,关于这一点的好处是,在将来添加新策略时,我永远不会再次触及StrategyResolver。
这非常简单。非常干净,我将Unity的依赖性保持在最低限度。我唯一一次触及StrategyResolver是因为我决定改变容器技术,这是不太可能发生的。
希望这有帮助!
答案 2 :(得分:0)
在过去的几年里,我以多种形式实现了这一要求。首先让我们来看看你在帖子中看到的要点
假设运行时选择策略和使用IOC容器......假设它不是必须选择的单一策略。相反,我可能需要检索具有多种策略的对象图... [必须]将调用者绑定到IOC容器...每个新策略必须[不需要]手动添加到绑定列表中。 ..如果IOC容器只是提供了一些帮助,那就太好了。
我已经使用Simple Injector作为我选择的容器已经有一段时间了,这个决定的驱动因素之一就是它对泛型有广泛的支持。通过此功能,我们将实现您的要求。
我坚信代码应该说明一切,所以我会直接进入......
ContainerResolvedClass<T>
来证明Simple Injector找到了正确的实现并成功地将它们注入到构造函数中。这是课程ContainerResolvedClass<T>
的唯一原因。 (此类通过result.Handlers
公开为测试目的而注入的处理程序。)第一个测试要求我们为虚构的类Type1
获得一个实现:
[Test]
public void CompositeHandlerForType1_Resolves_WithAlphaHandler()
{
var container = this.ContainerFactory();
var result = container.GetInstance<ContainerResolvedClass<Type1>>();
var handlers = result.Handlers.Select(x => x.GetType());
Assert.That(handlers.Count(), Is.EqualTo(1));
Assert.That(handlers.Contains(typeof(AlphaHandler<Type1>)), Is.True);
}
第二个测试要求我们为虚构的类Type2
获得一个实现:
[Test]
public void CompositeHandlerForType2_Resolves_WithAlphaHandler()
{
var container = this.ContainerFactory();
var result = container.GetInstance<ContainerResolvedClass<Type2>>();
var handlers = result.Handlers.Select(x => x.GetType());
Assert.That(handlers.Count(), Is.EqualTo(1));
Assert.That(handlers.Contains(typeof(BetaHandler<Type2>)), Is.True);
}
第三个测试要求我们为虚构的类Type3
获得两个实现:
[Test]
public void CompositeHandlerForType3_Resolves_WithAlphaAndBetaHandlers()
{
var container = this.ContainerFactory();
var result = container.GetInstance<ContainerResolvedClass<Type3>>();
var handlers = result.Handlers.Select(x => x.GetType());
Assert.That(handlers.Count(), Is.EqualTo(2));
Assert.That(handlers.Contains(typeof(AlphaHandler<Type3>)), Is.True);
Assert.That(handlers.Contains(typeof(BetaHandler<Type3>)), Is.True);
}
这些测试似乎符合您的要求,而且最重要的是解决方案中没有任何容器受到伤害。
技巧是使用参数对象和标记接口的组合。参数对象包含行为的数据(即IHandler
),标记接口控制哪些行为作用于哪些参数对象。
以下是标记接口和参数对象 - 您会注意到Type3
标有两个标记接口:
private interface IAlpha { }
private interface IBeta { }
private class Type1 : IAlpha { }
private class Type2 : IBeta { }
private class Type3 : IAlpha, IBeta { }
以下是行为(IHandler<T>
):
private interface IHandler<T> { }
private class AlphaHandler<TAlpha> : IHandler<TAlpha> where TAlpha : IAlpha { }
private class BetaHandler<TBeta> : IHandler<TBeta> where TBeta : IBeta { }
这是找到开放通用的所有实现的唯一方法:
public IEnumerable<Type> GetLoadedOpenGenericImplementations(Type type)
{
var types =
from assembly in AppDomain.CurrentDomain.GetAssemblies()
from t in assembly.GetTypes()
where !t.IsAbstract
from i in t.GetInterfaces()
where i.IsGenericType
where i.GetGenericTypeDefinition() == type
select t;
return types;
}
这是为我们的测试配置容器的代码:
private Container ContainerFactory()
{
var container = new Container();
var types = this.GetLoadedOpenGenericImplementations(typeof(IHandler<>));
container.RegisterAllOpenGeneric(typeof(IHandler<>), types);
container.RegisterOpenGeneric(
typeof(ContainerResolvedClass<>),
typeof(ContainerResolvedClass<>));
return container;
}
最后,测试类ContainerResolvedClass<>
private class ContainerResolvedClass<T>
{
public readonly IEnumerable<IHandler<T>> Handlers;
public ContainerResolvedClass(IEnumerable<IHandler<T>> handlers)
{
this.Handlers = handlers;
}
}
我意识到这篇文章很长,但我希望它能清楚地展示出你的问题的可能解决方案......
答案 3 :(得分:0)
我通常使用抽象工厂和命名绑定选项的组合。在尝试了许多不同的方法后,我发现这种方法是一个不错的平衡。
我所做的是创建一个基本上包装容器实例的工厂。请参阅Mark的article中名为基于容器的工厂的部分。正如他所说,我将这个工厂作为组合根的一部分。
为了使我的代码更简洁,更少“魔术字符串”,我使用枚举来表示不同的可能策略,并使用.ToString()方法进行注册和解析。
从这些方法的缺点:
通常将调用者绑定到IOC容器
在这种方法中,容器在工厂中被引用,它是组合根的一部分,所以这不再是一个问题(在我看来)。
。 。 。并且当然要求呼叫者知道关于策略的一些事情(例如 名称“Alpha”)。
必须手动将每个新策略添加到列表中 绑定。这种方法不适合处理多个 对象图中的策略。简而言之,它不符合 要求。
在某些时候,需要编写代码来确认提供实现的结构(容器,提供者,工厂等)与需要它的代码之间的映射。除非你想使用纯粹基于惯例的东西,否则我认为你不能解决这个问题。
每个策略的构造函数可能有不同的需求。但是现在构造函数注入的责任已从容器转移到抽象工厂。换句话说,每次添加新策略时,可能都需要修改相应的抽象工厂。
这种方法完全解决了这个问题。
大量使用策略意味着大量创建抽象工厂。[...]
是的,每组策略都需要一个抽象工厂。
如果这是一个多线程应用程序并且“环境上下文”确实由线程本地存储提供,那么当对象使用注入的抽象工厂来创建它所需的类型时,它可能在不同的线程上运行,该线程无法访问必要的线程本地存储值。
这将不再是一个问题,因为不会使用TLC。
我觉得没有一个完美的解决方案,但这种方法对我来说效果很好。
答案 4 :(得分:0)
我会实现这样的东西。
public interface IAbstractFactory
{
IFiledAppSettingsFactory this[Provider provider] { get; }
}
public Enum : int
{
One =1, Two =2, Three =3
}
internal class AbstractFactory : IAbstractFactory
{
public AbstractFactory(/** dependencies **/)
{
}
private readonly IReadOnlyDictionary<Provider, IFactory> services
= new Dictionary<Provider, IFactory>
{
{ Provider.One , new Factory1(/** dependencies comming from AbstractFactory **/) },
{ Provider.Two , new Factory2(/** no dependencies **/) },
{ Provider.Three, new Factory3(/** maybe more dependencies comming from AbstractFactory **/) },
};
IFactory IAbstractFactory.this[Provider provider] => this.services[provider];
}
internal sealed class Factory1: IFactory
{
internal FiledSelfFactory(/** any dependencies will come from AbstractFactory **/)
{
}
}
internal sealed class Factory2: IFactory
{
internal FiledSelfFactory(/** any dependencies will come from AbstractFactory **/)
{
}
}
internal sealed class Factory3: IFactory
{
internal FiledSelfFactory(/** any dependencies will come from AbstractFactory **/)
{
}
}
public static void AddAppSettings(this IServiceCollection serviceDescriptors)
{
serviceDescriptors.AddSingleton<IAbstractFactory, AbstractFactory>();
}
public class Consumer
{
private readonly IFactory realFactory;
public Consumer(IIAbstractFactory factory)
{
realFactory = factory[Provider.One]
}
}