插件架构可扩展性

时间:2012-05-31 17:33:56

标签: c# mef

我正在与MEF合作以获得插件架构。我想以一定的可扩展性进行设计。我想扩展初始化。

我所拥有的是一个“驱动程序”,它从某些来源重复收集数据。这些是我的插件。每个插件都需要初始化。现在我有一个这些插件需要实现的接口。

interface IDriverLiveCollection
{
    ILiveCollection GetCollection(ILog logger, IDriverConfig config);
}

此接口基本上是从插件创建ILiveCollection的实例。为了更好地理解ILiveCollection看起来像这样。

interface ILiveCollection
{
    void GetData(Parameter param, DataWriter writer);

    void ShutDown();
}

还有初始化循环:

foreach(IDriverConfig config in DriverConfigs)
{
    //Use MEF to load correct driver
    var collector = this.DriverLoader(config.DriverId).GetCollection(new Logger, config);

    // someTimer is an IObservable<Parameter> that triggers to tell when to collect data.
    someTimer.Subscribe((param)=> collector.GetData(param, new DataWriter(param)));
}

问题是某些驱动程序可能需要比其配置更多的信息才能进行初始化。例如,某些驱动程序需要在初始化期间为它们提供一组参数。

我可以轻松地将界面扩展为现在的样子:

interface IDriverLiveCollection
{
    ILiveCollection GetCollection(ILog logger, IDriverConfig config, IEnumerable<Parameter> params);
}

此方法的缺点是公共接口已更改,现在我需要重新编译每个驱动程序,即使之前没有任何需要此参数列表才能运行。我打算有大量的驱动程序,我也无法控制谁编写驱动程序。

我想到了另一个解决方案。我可以创建接口,在我调用Get Collection之间和我订阅定时器之间的循环中,我可以检查ILiveCollection对象是否也扩展了其中一个接口:

interface InitWithParameters
{
    void InitParams(IEnumerable<Parameter> params);
}

在我的循环中:

foreach(IDriverConfig config in DriverConfigs)
{
    //Use MEF to load correct driver
    var collector = this.DriverLoader(config.DriverId).GetCollection(new Logger, config);

    // Check to see if this thing cares about params.
    if(collector is InitWithParameters)
    {
        ((InitWithparameters)collector).InitParams(ListOfParams);
    }

    // Continue with newly added interfaces.

    // someTimer is an IObservable<Parameter> that triggers to tell when to collect data.
    someTimer.Subscribe((param)=> collector.GetData(param, new DataWriter(param)));
}

这里的不同之处在于我不需要重新编译每个驱动程序以使其工作。旧驱动程序将不是InitWithParameters类型,并且不会以这种方式调用,而新驱动程序将能够利用新接口。如果旧驱动程序想要利用它,那么它可以简单地实现该接口并重新编译。底线:除非他们想要功能,否则我不需要重新编译驱动程序。

我已经认识到的缺点是:我显然需要重新编译哪个程序在这个循环中。当新驱动程序与旧版本的程序一起使用时,会出现版本控制问题,这可能会导致某些问题。最后,随着这些事情的发展,我必须在循环中保存一个包含程序中每种可能类型的巨大列表。

有更好的方法吗?

修改其他信息:

我试图在IDriverLiveCollection上使用MEF,而不是在ILiveCollection上,因为IDriverLiveCollection允许我使用自定义初始化参数构造特定的ILiveCollection。

可以有2个相同类型的ILiveCollections(2个FooLiveCollections),每个ILiveCollections具有不同的ILog和IDriverConfig,并且可能具有IEnumerable。我希望能够在“初始化循环”期间指定这些,而不是在插件组成时指定。

1 个答案:

答案 0 :(得分:2)

如果您只是直接在[ImportMany]界面上使用ILiveCollection,则可以通过[ImportingConstructor]属性处理整个基础架构。

这允许您的插件准确指定它们需要构造的内容,而不必在以后提供和构造类型。

实际上,您的主机应用程序只需要:

// This gets all plugins
[ImportMany]
IEnumerable<ILiveCollection> LiveCollections { get; set; }

然后每个插件的类型都会导出它,即:

[Export(typeof(ILiveCollection))]
public class FooLiveCollection : ILiveCollection
{
    [ImportingConstructor]
    public FooLiveCollection(ILog logger, IDriverConfig config)
    {
       // ...

或者,或者,插件可以省略一个构造函数参数,或添加laters(不影响以前的插件),即:

    [ImportingConstructor]
    public BarLiveCollection(ILog logger, IDriverConfig config, IBarSpecificValue barParam)
    {
       // ...