【问题标题】:How to make pluggable static classes如何制作可插入的静态类
【发布时间】:2012-10-03 14:55:58
【问题描述】:

我知道如何在 c# 中制作可插入的东西。定义一个接口,Activator.CreateInstance(<class>) 等。或者我可以有一个明确创建插入类实例的代码路径。方法很多。

但是,如果我想要使可插拔的服务是静态的(我知道我可以重构它不是,但这不是问题的重点)

具体例子。我有一个提供磁盘 I/O 抽象的类(读取文件、列表目录……)。现在我想要这个抽象的不同实现,它提供来自真实 FS、数据库的文件。

根据 Olivier Jacot-Descombes 的回复,我将有一个像这样的 FileSystem 类(这是真实的)

public static class FileSystem
{
    static IFSImplemenation s_imple;
    static FileSystem()
    {
        if(<some system setting>)
            // converted to singleton instead of static
            s_imple = new OldFileSystem() 
        else
            s_imple = new DbFileSystem();
    }

    public static byte[] ReadFile(string path)
    {
        return s_imple.ReadFile(path);
    }

    ...
}

重申一下 - 我有大量代码不想更改,因此保持调用签名相同很重要 - 此解决方案实现了这一点

【问题讨论】:

  • 如果你想要抽象和按合同设计等,那么你想要使用实例而不是静态方法。您知道这并不能改变这样一个事实,即为此目的使用 static 构造是完全错误的方法。
  • 你真的,真的想看看依赖注入;您获得了换出实现的能力,但这并不容易,很可能需要重新编译使用者(依赖注入不需要重新编译)并且根本不可测试。
  • @casperOne 我认为我们基于实例化类的所有 DI 系统
  • 是的,但是 DI 实现会为您进行实例化,通常通过即时编译的代码或反射(通常是前者)基于模式(找到接口和具体之间的映射)类型)。如果你的类都在库中并使用契约,契约不会改变,库也不需要重新编译,只需要在你设置 DI 的地方。其他一切都保持不变。

标签: c# .net plugins static-classes


【解决方案1】:

你不能,这是 .NET 和大多数面向对象的系统/语言中类型系统的限制。

原因是“可插拔的东西”(正如您所指的那样)需要polymorphism 才能有效。

您定义一个合同(一个接口,在 .NET 中),然后您实施该合同。您的系统只处理合同

静态成员在 .NET 中不是多态的,因此您永远无法让它们实现合同。

对于您的磁盘 I/O 抽象示例,您不会为此创建静态类,而是创建一个接口并实现该接口,并在接口周围传递。

当然,使用接口的好处是更容易测试合同实施的双方:

  • 在合约是客户端的情况下,您可以轻松地模拟接口并将模拟传递给合约的使用者(这就是您可能开始考虑 dependency injection 的地方)
  • 您可以与系统的其余部分分开测试实施。

在您的磁盘 I/O 抽象的情况下,对于调用您的合约的所有内容,您永远不必担心实际上会接触文件系统,您只需让合约运行(通过适当的模拟设置),就好像它正在接触文件系统。

如果您有一个通过静态成员公开的现有服务,并且您希望能够将其与另一个实现交换,那么您必须执行以下操作:

  • 创建定义服务操作的抽象(合同/抽象类型)。
  • 创建一个实现,将调用从抽象实现的实例转发到静态方法。
  • 使用实现实例重新连接对服务的所有静态调用。在这一点上,你真的必须使用某种依赖注入;如果你不这样做,你只是在调用站点创建具体的实现,这几乎是一样的(如果你想使用另一个实现,你必须再次更改每个调用站点)。

【讨论】:

  • 我完全理解合约、多态等。可插拔的东西不需要多态。可插入是指服务的运行时可替换实现。正如我在我的问题中所说 - 我知道使用接口是正确的方法,但这不是问题的重点。我想看看人们是否有想法让运行时可替换静态
  • @pm100 您似乎错过了答案的重点,将一件事换成另一件事的唯一方法是提供接口/抽象类的另一种实现。您不能交换静态的东西,这在 .NET 中是不可能的。您的问题的基本答案是“不”,这个答案是我解释原因的最佳尝试。
  • @pm100 添加了更多内容来说明如果您想让静态服务成为可插拔服务该怎么做,但我觉得它仍然没有给您想要的答案 ,不幸的是(但我无法改变 .NET 以这种方式工作的方式)。
【解决方案2】:

静态类不是“可插入的”。如果您希望能够将其换出,则需要对其进行重构以使用可以注入的接口。

有些类有一个静态的CurrentDefault 参数,例如ServiceLocator。然后,您可以使用 ServiceLocator.Current 在静态上下文中访问当前的 IServiceLocator

您可以更进一步并隐藏Current 对象。虽然我不建议这样做,但它可以避免重构所有代码的需要。但是,这种方法的维护要高得多,因为Foo 没有实现IFoo

假设你有一个静态类Foo

static class Foo
{
    public static string GetBar() { return "Bar"; }
}

你可以做一个接口IFoo

interface IFoo
{
    string GetBar();
}

现在将您的类 Foo 更改为以下内容

static class Foo 
{
    private static IFoo _foo;

    public static void SetFoo(IFoo foo) { _foo = foo; }

    public static string GetBar() { return _foo.GetBar(); }
}

【讨论】:

    【解决方案3】:

    使用你的静态类作为非静态实现的外观

    public static class DB
    {
        private static IDbInterface _implementation;
    
        public static void SetImplementation(IDbInterface implementation)
        {
            _implementation = implementation;
        }
    
        public static Customer GetCustomerByID(int custId)
        {
            return _implementation.GetCustomerByID(custId);
        }
    
        ...
    }
    

    【讨论】:

    • 我喜欢这个。实际上我认为在类的静态构造函数中完成重定向(基于一些环境设置更好) - 然后根本不需要更改调用代码
    【解决方案4】:

    让你的静态类驻留在宿主应用程序中,实际上是一个工厂:通过接口返回插件提供的实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-24
      • 1970-01-01
      • 2016-03-26
      • 1970-01-01
      • 2012-08-10
      • 1970-01-01
      • 2023-03-22
      • 2020-04-11
      相关资源
      最近更新 更多