代码之家  ›  专栏  ›  技术社区  ›  Clinton Pierce

将代码从反射移植到使用接口,有些API没有实现

  •  0
  • Clinton Pierce  · 技术社区  · 15 年前

    我有一些旧的C插件代码,它们是通过反射严格实现的。在修复某些C 2.0->4.0兼容性问题时( "Load from Remote Source" )我决定去掉旧的反射代码,用接口替换它。接口是必需的,因为插件现在需要加载到自己的AppDomain中,然后进行编组。

    我可以浏览数百个插件的源代码,让它们都实现新的 IPlugin 界面简单的搜索和替换。除了在 至关重要的地方。 我在找一个容易的地方。

    方法 RunPlugin() 可以用两种方法之一实现,但不能同时使用两种方法:带参数或不带参数。如果我在接口中包含这一点,我就必须在每个插件中实现这两个功能。调用方根据调用一个或没有参数的方法 执行哪一个 . 调用程序集现在通过反射来解决这个问题。

    为了避免这样的情况,我可以为插件创建一个包装类,这个包装实现了接口,但是随后我必须对每个插件进行大量的编辑,以便为API的许多方法中的每个方法包含一个覆盖。

    一些示例代码(这不一定有效!现在一切都在转变中!):

    接口(示例):

    // In IPlugin.cs / IPlugin.dll
    namespace Plugin
    {
        public interface IPlugin
        {
               // Many, many happy API things like this...
               void SetupOptions(Hashtable options);
               // (examples elided)
    
               // And then these two... either one or the other
               // is implemented, but not both.
               XmlDocument RunPlugin(Updater u);
               XmlDocument RunPlugin();
        }
    }
    

    被调用的程序集…我有 太多了 这些。我可以很容易地添加“:iplugin”。显然,这不会编译,因为它没有实现一个参数 RunPug in() .

    namespace Plugin
    {
          public class FileMaintenance : IPlugin
          {
              public void SetupOptions(Hashtable options)
              {  // Code elided
              } 
    
              public XmlDocument RunPlugin()
              {  // Code elided
              }
          }
    }
    

    最后,调用代码。这实际上是它在反射代码中的查看方式:

    public XmlDocument RunPlugin(PluginRunner.Updater u)
    {
        Type [] paramTypes = new Type [0];
        MethodInfo runInfo = repType.GetMethod("RunPlugin", paramTypes);
        if (runInfo == null)
        {
            paramTypes = new Type [1];
            paramTypes[0] = u.GetType();
            runInfo = repType.GetMethod("RunPlugin", paramTypes);
            if (runInfo == null)
                throw new Exception;
        }
        Object[] parameters;
        if ( paramTypes.Length == 0)
            parameters = new Object[0];
        else
        {
            parameters = new Object[1];
            parameters[0] = u;
        }
        Object returnVal;
        try 
        {
            returnVal = runInfo.Invoke(repObj,parameters);
        }
        catch (Exception e)
        {
                }
             // Rest of code omitted
     }
    

    记住:我在寻找一个很好的平衡,在修复这个旧代码的正确方法和手工编辑代码的最少数量之间。

    4 回复  |  直到 15 年前
        1
  •  3
  •   Adam Robinson    15 年前

    我主张创建两个附加接口:

    public interface IRunnablePlugin : IPlugin
    {
        XmlDocument RunPlugin();
    }
    
    public interface IParamRunnablePlugin : IPlugin
    {
        XmlDocument RunPlugin(object parameter);
    }
    

    然后让你所有的插件实现其中的一个或另一个。你唯一需要区别的时候就是打电话的时候 RunPlugin . 其他时候你可以把它称为一个简单的 IPlugin .

    例如,要执行呼叫,您需要执行如下操作:

    IPlugin plugin = ...;
    
    IRunnablePlugin runnable = plugin as IRunnablePlugin;
    IRunnableParamPlugin param = plugin as IRunnableParamPlugin;
    
    XmlDocument output;
    
    if(param != null)
    {
        output = param.RunPlugin(parameter);
    }
    else if(runnable != null)
    {
        output = runnable.RunPlugin();
    }
    else
    {
        throw new InvalidOperationException();
    }
    

    注意有 技术上 没有限制,允许开发人员只实现其中一个版本,但希望这不是问题。在这段代码中,首先检查参数化版本的存在,然后检查无参数化版本,如果两者都找不到,则抛出异常。

        2
  •  1
  •   Matt Greer    15 年前

    不幸的是有一个或另一个 RunPlugin 方法是从getgo添加到这个设计中的阿基里斯治疗。这是一个不稳定的设计决策,很难处理。

    一种可能是将两个重载添加到 IPlugin 让插件通过抛出 NotImplementedException . 这可能不会给你带来什么,也不会给你带来什么。

    另一种可能是两个接口, 插件接口 IPluginWithArgs ,并检测给定插件正在实现的接口,然后从那里开始。也很丑陋。

    另一种可能是扩展方法 public static void RunPlugin(this IPlugin plugin) 这基本上隐藏和整理了你已经做的反射。我也不认为这能给你带来什么。

        3
  •  0
  •   Adrian Regan    15 年前

    您可以有一个这样的基本接口:

    public interface IPlugin {
               // Many, many happy API things like this...
               void SetupOptions(Hashtable options);
               // (examples elided)
    
               XmlDocument RunPlugin();
    }
    
    //
    // And another interface extending the IPlugin that defines the update version
    //
    
    public interface IUpdaterPlugin : IPlugin {
               XmlDocument RunPlugin(Updater u);
    
    }
    

    运行适当的插件…

    if( plugin is IUpdaterPlugin)
        plugin.RunPlugin(updater);
    else if(plugin is IPlugin)
        plugin.RunPlugin();
    
        4
  •  0
  •   Joseph    15 年前

    所以,你说…

    为了避免这种情况,我可以为插件创建一个包装类,这个包装 实现接口,但随后我必须大量编辑 插件包含对API许多方法中的每个方法的重写。

    我不太确定我是否理解包装类的问题,但是我们可能在考虑不同的事情。

    我正在考虑的包装类将采用参数为零的接口,并使其看起来像一个参数的接口。当调用“runplugin”时,该参数将被忽略。

    这将删除接口类型上的任何分支,您只需创建一个类——它可以包装任何无参数接口的实例。所以每个插件不应该需要任何特殊的代码。只需在创建插件实例时创建包装。

    (顺便说一下,这是 Adapter pattern )