我有一系列应用程序提供可扩展(即不固定)的变量集,可供各种插件使用。
例如:
插件可以使用这些的任意组合。
示例插件可以是:
我想要实现的是
奖励是允许插件可选地需要变量,例如一个需要4的插件,并且可选地使用3.如果可用(但也可以使用)。
我想实现某种“动态依赖注入”。 让我用一个用例来解释它。
我正在构建一组将用于一系列应用程序的库。 每个应用程序都可以提供一组不同的变量,这些变量可供需要这些变量的某些“处理程序”使用。 根据具体的可用变量,必须确定可用处理程序的数量,因为只有在可以访问所有必需变量的情况下才能使用处理程序。 此外,我正在寻找一种方法,使调用尽可能安全。 编译时可能不可能,但“检查一次,之后永远不会失败”就可以了。
下面是第一个草图。在这个阶段,一切都还可以改变。
class DynamicDependencyInjectionTest
{
private ISomeAlwaysPresentClass a;
private ISomeOptionalClass optionA;
private ISomeOtherOptionalClass optionB;
private ISomeMultipleOption[] multi;
private IDependentFunction dependentFunction;
void InvokeDependency()
{
// the number of available dependencies varies.
// some could be guaranteed, others are optional, some maybe have several instances
var availableDependencies = new IDependencyBase[] {a, optionA, optionB}.Concat(multi).ToArray();
//var availableDependencies = new IDependencyBase[] { a };
//var availableDependencies = new IDependencyBase[] { a, optionA }.ToArray();
//var availableDependencies = new IDependencyBase[] { a, optionB }.ToArray();
//var availableDependencies = new IDependencyBase[] { a , multi.First() };
//ToDo
// this is what I want to do
// since we checked it before, this must always succeed
somehowInvoke(dependentFunction, availableDependencies);
}
void SetDependentFunction(IDependentFunction dependentFunction)
{
if (! WeCanUseThisDependentFunction(dependentFunction))
throw new ArgumentException();
this.dependentFunction = dependentFunction;
}
private bool WeCanUseThisDependentFunction(IDependentFunction dependentFunction)
{
//ToDo
//check if we can fulfill the requested dependencies
return true;
}
/// <summary>
/// Provide a list which can be used by the user (e.g. selected from a combobox)
/// </summary>
IDependentFunction[] AllDependentFunctionsAvailableForThisApplication()
{
IDependentFunction[] allDependentFunctions = GetAllDependentFunctionsViaReflection();
return allDependentFunctions.Where(WeCanUseThisDependentFunction).ToArray();
}
/// <summary>
/// Returns all possible candidates
/// </summary>
private IDependentFunction[] GetAllDependentFunctionsViaReflection()
{
var types = Assembly.GetEntryAssembly()
.GetTypes()
.Where(t => t.IsClass && typeof (IDependentFunction).IsAssignableFrom(t))
.ToArray();
var instances = types.Select(t => Activator.CreateInstance(t) as IDependentFunction).ToArray();
return instances;
}
private void somehowInvoke(IDependentFunction dependentFunction, IDependencyBase[] availableDependencies)
{
//ToDo
}
}
// the interfaces may of course by changed!
/// <summary>
/// Requires a default constructor
/// </summary>
interface IDependentFunction
{
void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies);
Type[] RequiredDependencies { get; }
}
interface IDependencyBase { }
interface ISomeAlwaysPresentClass : IDependencyBase { }
interface ISomeOptionalClass : IDependencyBase { }
interface ISomeOtherOptionalClass : IDependencyBase { }
interface ISomeMultipleOption : IDependencyBase { }
class BasicDependentFunction : IDependentFunction
{
public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
{
;
}
public Type[] RequiredDependencies
{
get { return new[] {typeof(ISomeAlwaysPresentClass)}; }
}
}
class AdvancedDependentFunction : IDependentFunction
{
public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
{
;
}
public Type[] RequiredDependencies
{
get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass) }; }
}
}
class MaximalDependentFunction : IDependentFunction
{
public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
{
;
}
public Type[] RequiredDependencies
{
// note the array in the type of ISomeMultipleOption[]
get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass), typeof(ISomeOtherOptionalClass), typeof(ISomeMultipleOption[]) }; }
}
}
答案 0 :(得分:4)
保持简单。让插件依赖构造函数注入,这样做的好处是构造函数静态地宣告每个类的依赖关系。然后使用Reflection来弄清楚你可以创建什么。
例如,假设您有三个服务:
public interface IFoo { }
public interface IBar { }
public interface IBaz { }
此外,假设存在三个插件:
public class Plugin1
{
public readonly IFoo Foo;
public Plugin1(IFoo foo)
{
this.Foo = foo;
}
}
public class Plugin2
{
public readonly IBar Bar;
public readonly IBaz Baz;
public Plugin2(IBar bar, IBaz baz)
{
this.Bar = bar;
this.Baz = baz;
}
}
public class Plugin3
{
public readonly IBar Bar;
public readonly IBaz Baz;
public Plugin3(IBar bar)
{
this.Bar = bar;
}
public Plugin3(IBar bar, IBaz baz)
{
this.Bar = bar; ;
this.Baz = baz;
}
}
很明显,Plugin1
需要IFoo
,Plugin2
需要IBar
和IBaz
。第三个类Plugin3
稍微有点特殊,因为它有一个可选的依赖项。虽然需要 IBar
,但如果它可用,它也可以使用IBaz
。
您可以定义一个使用一些基本反射的Composer来检查是否可以根据可用服务创建各种插件的实例:
public class Composer
{
public readonly ISet<Type> services;
public Composer(ISet<Type> services)
{
this.services = services;
}
public Composer(params Type[] services) :
this(new HashSet<Type>(services))
{
}
public IEnumerable<Type> GetAvailableClients(params Type[] candidates)
{
return candidates.Where(CanCreate);
}
private bool CanCreate(Type t)
{
return t.GetConstructors().Any(CanCreate);
}
private bool CanCreate(ConstructorInfo ctor)
{
return ctor.GetParameters().All(p =>
this.services.Contains(p.ParameterType));
}
}
如您所见,您使用一组可用服务配置Composer
实例,然后可以使用候选列表调用GetAvailableClients
方法以获取一系列可用插件。< / p>
您可以轻松扩展Composer
类,以便能够创建所需插件的实例,而不是只告诉您哪些插件可用。
您可能会在某些DI容器中找到此功能。 IIRC,Castle Windsor公开了一个 Tester / Doer API,如果MEF也支持这样的功能,我也不会感到惊讶。
以下xUnit.net参数化测试表明上述Composer
有效。
public class Tests
{
[Theory, ClassData(typeof(TestCases))]
public void AllServicesAreAvailable(
Type[] availableServices,
Type[] expected)
{
var composer = new Composer(availableServices);
var actual = composer.GetAvailableClients(
typeof(Plugin1), typeof(Plugin2), typeof(Plugin3));
Assert.True(new HashSet<Type>(expected).SetEquals(actual));
}
}
internal class TestCases : IEnumerable<Object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] {
new[] { typeof(IFoo), typeof(IBar), typeof(IBaz) },
new[] { typeof(Plugin1), typeof(Plugin2), typeof(Plugin3) }
};
yield return new object[] {
new[] { typeof(IBar), typeof(IBaz) },
new[] { typeof(Plugin2), typeof(Plugin3) }
};
yield return new object[] {
new[] { typeof(IFoo), typeof(IBaz) },
new[] { typeof(Plugin1) }
};
yield return new object[] {
new[] { typeof(IFoo), typeof(IBar) },
new[] { typeof(Plugin1), typeof(Plugin3) }
};
yield return new object[] {
new[] { typeof(IFoo) },
new[] { typeof(Plugin1) }
};
yield return new object[] {
new[] { typeof(IBar) },
new[] { typeof(Plugin3) }
};
yield return new object[] {
new[] { typeof(IBaz) },
new Type[0]
};
yield return new object[] {
new Type[0],
new Type[0]
};
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
答案 1 :(得分:0)
经验教训:
最初我以为我需要一个per调用方法注入,这很复杂,因为:
然后我通过添加所需的依赖项作为属性切换到一种属性注入,每个属性绑定到一个接口,以便可以轻松地发现所需的依赖项。调用本身是无参数的。
可能正确的方法是使用构造函数注入,因此可以使用标准工具。调用本身也是无参数的,因此它非常适合接口。
以下是Mark解决方案的更完整版本,包括解析组件。它使用Castle.Windsor,xUnit,Shouldly和Resharper的NotNull, CanBeNull
属性。
通过注入一个解析工具工厂来删除对Castle.Windsor的直接依赖是必要的(因为它必须接受来自主机的实例,我们不能直接传入解析器)。
public interface IFoo { }
public interface IBar { }
public interface IBaz { }
/// <summary>
/// Needed to invoke the plugin
/// </summary>
public interface IPlugin
{
void Invoke();
}
public class Plugin1 : IPlugin
{
public readonly IFoo Foo;
public Plugin1([NotNull] IFoo foo)
{
if (foo == null) throw new ArgumentNullException("foo");
this.Foo = foo;
}
public void Invoke()
{
;
}
}
public class Plugin2 : IPlugin
{
public readonly IBar Bar;
public readonly IBaz Baz;
public Plugin2([NotNull] IBar bar, [NotNull] IBaz baz)
{
if (bar == null) throw new ArgumentNullException("bar");
if (baz == null) throw new ArgumentNullException("baz");
this.Bar = bar;
this.Baz = baz;
}
public void Invoke()
{
;
}
}
public class Plugin3 : IPlugin
{
public readonly IBar Bar;
public readonly IBaz Baz;
public Plugin3([NotNull] IBar bar, [CanBeNull] IBaz baz = null)
{
if (bar == null) throw new ArgumentNullException("bar");
this.Bar = bar; ;
this.Baz = baz;
}
public void Invoke()
{
;
}
}
public class Bar : IBar
{
}
public class SampleHostTest
{
[Fact]
void SampleHostCanResolvePlugin3ButNot1And2()
{
var bar = new Bar();
var plugins = Assembly.GetAssembly(typeof(SampleHost))
.GetTypes()
.Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t))
.ToArray();
var sut = new SampleHost(bar, plugins);
sut.IsPluginSupported(typeof(Plugin1)).ShouldBeFalse();
sut.IsPluginSupported(typeof(Plugin2)).ShouldBeFalse();
sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue();
}
[Fact]
void ResolvePlugin3()
{
var bar = new Bar();
var plugins = Assembly.GetAssembly(typeof(SampleHost))
.GetTypes()
.Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t))
.ToArray();
var sut = new SampleHost(bar, plugins);
sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue();
sut.CreateAndInvokePlugin(typeof(Plugin3));
// no exception => succeeded
}
}
public class SampleHost
{
private readonly IBar bar;
private readonly IWindsorContainer container;
private Type[] plugins;
public SampleHost(IBar bar, IEnumerable<Type> plugins)
{
this.bar = bar;
this.plugins = plugins.ToArray();
this.container = new WindsorContainer();
container.Register(Component.For<IBar>().Instance(this.bar));
foreach (var plugin in this.plugins)
{
container.Register(Component.For(plugin).ImplementedBy(plugin).LifestyleTransient());
}
}
public bool IsPluginSupported(Type type)
{
var result = container.Kernel.HasComponent(type) &&
container.Kernel.GetHandler(type).CurrentState == HandlerState.Valid;
return result;
}
public void CreateAndInvokePlugin(Type type)
{
Assert.True(IsPluginSupported(type));
var plugin = container.Resolve(type)as IPlugin;
Debug.Assert(plugin != null, "plugin != null");
plugin.Invoke();
}
}