C# 有没有更好的方法来伪造静态类接口?

C# 有没有更好的方法来伪造静态类接口?,c#,design-patterns,factory-pattern,C#,Design Patterns,Factory Pattern,这种设计模式有意义吗?我最初有一个静态类,它为每个实现的算法返回一个HashFunction public delegate int HashFunction(int seed, params int[] keys); 但后来我意识到我需要几个元数据以及每个算法,所以我创建了这个接口: public interface IHashAlgorithm { HashFunction CalculateHash { get; } NoiseFunction CalculateNois

这种设计模式有意义吗?我最初有一个静态类,它为每个实现的算法返回一个
HashFunction

public delegate int HashFunction(int seed, params int[] keys);
但后来我意识到我需要几个元数据以及每个算法,所以我创建了这个接口:

public interface IHashAlgorithm
{
    HashFunction CalculateHash { get; }
    NoiseFunction CalculateNoise { get; }
    int Maximum { get; }
    int Minimum { get; }
}
public delegate double NoiseFunction(int seed, params int[] keys);

internal sealed class HashAlgorithm : IHashAlgorithm
{
    public HashAlgorithm(HashFunction function, int min, int max)
    {
        CalculateHash = function;
        Minimum = min;
        Maximum = max;
    }

    public HashFunction CalculateHash { get; private set; }

    public NoiseFunction CalculateNoise
    {
        get { return Noise; }
    }

    public int Maximum { get; private set; }
    public int Minimum { get; private set; }

    private double Noise(int seed, params int[] keys)
    {
        return ((double)CalculateHash(seed, keys) - Minimum)/
            ((double)Maximum - Minimum + 1);
    }
}
内部类实现所需的接口:

public interface IHashAlgorithm
{
    HashFunction CalculateHash { get; }
    NoiseFunction CalculateNoise { get; }
    int Maximum { get; }
    int Minimum { get; }
}
public delegate double NoiseFunction(int seed, params int[] keys);

internal sealed class HashAlgorithm : IHashAlgorithm
{
    public HashAlgorithm(HashFunction function, int min, int max)
    {
        CalculateHash = function;
        Minimum = min;
        Maximum = max;
    }

    public HashFunction CalculateHash { get; private set; }

    public NoiseFunction CalculateNoise
    {
        get { return Noise; }
    }

    public int Maximum { get; private set; }
    public int Minimum { get; private set; }

    private double Noise(int seed, params int[] keys)
    {
        return ((double)CalculateHash(seed, keys) - Minimum)/
            ((double)Maximum - Minimum + 1);
    }
}
在某种公共静态工厂类中创建并返回:

public static class Hashing
{
    private static readonly IHashAlgorithm MurmurHash2Instance =
        new HashAlgorithm(MurmurHash2Hash, 0, int.MaxValue);

    private static readonly IHashAlgorithm ReSharperInstance =
        new HashAlgorithm(ReSharperHash, int.MinValue, int.MaxValue);

    public static IHashAlgorithm MurmurHash2
    {
        get { return MurmurHash2Instance; }
    }

    public static IHashAlgorithm ReSharper
    {
        get { return ReSharperInstance; }
    }

    private static int MurmurHash2Hash(int seed, params int[] keys)
    {
        //...
    }

    private static int ReSharperHash(int seed, params int[] keys)
    {
        //...
    }
}
我更希望能够在每个算法的静态类上实现
IHashAlgorithm

public static class MurmurHash2 : IHashAlgorithm
{
    public static int Hash(int seed, params int[] keys) {...}

    //...
}

不幸的是,C#不允许这样做,所以这是我试图绕过它的方法。

没有办法伪造静态类接口,很多时候,当我认为我需要一个静态类接口时,我实际上需要常用的实例接口。你不能在C#中传递一个静态类的“实例”,也没有办法给一个函数一个“静态”接口,甚至一个静态“类”来使用它的静态方法。当您调用静态方法时,它总是显式的,并且您将方法“硬链接”到您调用的静态类,这不是一件好事

基于静态方法的可变性很难进行单元测试。依赖于这种可变性的类灵活性较差。想象一下,如果某个函数会显式地使用静态类中的某个算法。这样的函数会显式地将自身耦合到特定的算法

public class SomeBusinessLogic
{
   public Result HandleDocument(IDocument doc)
   {
       // some transformations...

       int hash = Hashing.ReSharperHash.CalculateHash(seed, doc.Properties);

       // some other code ...
   }
}
public class SomeBusinessLogic
{
   // injection in constructor
   public SomeBusinessLogic(IHashingAlgorithm hashing)
   {
       // put hashing in a field of the class
   }

   // OR injection in method itself, if hashing is only used in this method
   public Result HandleDocument(IDocument doc, IHashingAlgorithm hashing)
   {
       // some transformations...

       int hash = hashing.CalculateHash(seed, doc.Properties);

       // some other code ...
   }
}
那有什么不对吗

  • 该类从未明确声明它依赖于哈希。您需要了解它的实现,才能对此进行推理。在这种情况下,这可能不是很重要,但如果其中一个哈希算法非常慢呢?或者如果它需要磁盘上的一些外部文件?或者它是否连接到某个外部哈希服务?调用
    HandleDocument
    函数时,它可能会意外失败

  • 如果要对特定文档使用其他哈希算法,则必须更改代码

  • 当您对它进行单元测试时,您需要同时测试文档处理逻辑和散列逻辑(应该已经由它自己的单元测试进行了测试)。如果您的测试将输出<代码>结果与资源中的某个值进行比较,并且该值包含哈希值,那么当您将其更改为其他哈希算法时,此函数的所有单元测试都将中断

  • public class SomeBusinessLogic
    {
       public Result HandleDocument(IDocument doc)
       {
           // some transformations...
    
           int hash = Hashing.ReSharperHash.CalculateHash(seed, doc.Properties);
    
           // some other code ...
       }
    }
    
    public class SomeBusinessLogic
    {
       // injection in constructor
       public SomeBusinessLogic(IHashingAlgorithm hashing)
       {
           // put hashing in a field of the class
       }
    
       // OR injection in method itself, if hashing is only used in this method
       public Result HandleDocument(IDocument doc, IHashingAlgorithm hashing)
       {
           // some transformations...
    
           int hash = hashing.CalculateHash(seed, doc.Properties);
    
           // some other code ...
       }
    }
    
    有什么更好的办法?提取一个抽象哈希函数的接口,并在需要进行哈希时显式地请求它。因此,只要算法是无状态的,您仍然可以将其实现为某种类型的单例,但是客户机代码将与哈希细节没有任何耦合。谁知道呢,也许有一天你会发现你需要一些参数化的散列算法,你可以在每次需要的时候创建一个新的算法实例

    我正在使用您的界面,但风格有所改变:

    public interface IHashAlgorithm
    {
        int CalculateHash(int seed, params int[] keys);
        int CalculateNoise(int seed, params int[] keys);
        int Maximum { get; }
        int Minimum { get; }
    }
    
    public static class StatelessHashAlgorithms
    {
        private static readonly IHashAlgorithm MurmurHash2Instance =
            new HashAlgorithm(MurmurHash2Hash, 0, int.MaxValue);
    
        private static readonly IHashAlgorithm ReSharperInstance =
            new HashAlgorithm(ReSharperHash, int.MinValue, int.MaxValue);
    
        public static IHashAlgorithm MurmurHash2
        {
            get { return MurmurHash2Instance; }
        }
    
        public static IHashAlgorithm ReSharper
        {
            get { return ReSharperInstance; }
        }
    
        private static int MurmurHash2Hash(int seed, params int[] keys)
        {
            //...
        }
    
        private static int ReSharperHash(int seed, params int[] keys)
        {
            //...
        }
    }
    
    public class SomeCustomHashing : IHashAlgorithm
    {
       public SomeCustomHashing(parameters)
       {
          //parameters define how hashing behaves
       }
    
       // ... implement IHashAlgorithm here
    }
    
    所有客户端代码都应该在需要哈希接口时显式地请求哈希接口,这称为依赖项注入,可以在类级别或方法级别上完成。然后,类的调用方或创建者将负责提供哈希算法

    public class SomeBusinessLogic
    {
       public Result HandleDocument(IDocument doc)
       {
           // some transformations...
    
           int hash = Hashing.ReSharperHash.CalculateHash(seed, doc.Properties);
    
           // some other code ...
       }
    }
    
    public class SomeBusinessLogic
    {
       // injection in constructor
       public SomeBusinessLogic(IHashingAlgorithm hashing)
       {
           // put hashing in a field of the class
       }
    
       // OR injection in method itself, if hashing is only used in this method
       public Result HandleDocument(IDocument doc, IHashingAlgorithm hashing)
       {
           // some transformations...
    
           int hash = hashing.CalculateHash(seed, doc.Properties);
    
           // some other code ...
       }
    }
    
    它解决了上述问题:

  • 该类显式声明它依赖于哈希。提供特定哈希算法的人都知道性能、资源、连接、异常等方面的预期

  • 您可以为每个业务逻辑实例或每个文档配置使用哪个哈希算法

  • 当您对它进行单元测试时,您可以提供散列的模拟实现,例如始终返回0(并检查该方法是否将预期值传递给散列算法)。这是您单独检查哈希和单独检查文档处理的结果

  • 因此,底线是,如果您在行为上有一些变化,请使用标准实例接口。它们背后的代码可以是静态的,也可以是非静态的,这无关紧要。重要的是使用变量行为的地方将保持灵活性、可扩展性和单元可测试性


    另外,还有一个方面是“你的领域是什么”。如果您正在编写一些业务应用程序,并且到处调用static
    Math.Sqrt(…)
    ,这很好,因为没有其他行为。但是,如果您正在编写一些数学库,并且您有几个不同的平方根实现,具有不同的算法或精度,您可能希望将它们包装到一个接口中,并作为接口实例传递,以便能够扩展。

    如果您需要模拟“静态接口”,我认为您应该使用make singleton,这看起来很像你正在做的事情,但奇怪的是,Ingenu是对的——单身是唯一的方法。当然,如果希望严格控制API,可以选择显式接口实现。