是否有更好的方法来伪造静态类接口?

时间:2011-10-16 02:42:22

标签: c# design-patterns factory-pattern

这种设计模式有多大意义吗?我最初有一个静态类,它为每个实现的算法返回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 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#不允许这样做,所以这是我尝试绕过它。

1 个答案:

答案 0 :(得分:2)

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

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

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

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

       // some other code ...
   }
}

嗯,那有什么不对?

  1. 该类从未明确声明它依赖于散列。你需要知道它的实现来推理它。在这种情况下,它可能不是很重要,但如果其中一个散列算法非常慢,该怎么办?或者如果它需要磁盘上的一些外部文件?或者,如果它连接到某些外部散列服务?调用HandleDocument函数时,它可能会意外失败。

  2. 如果您想对特定文档使用其他一些散列算法,则无法在不更改代码的情况下执行此操作。

  3. 当您进行单元测试时,您可以测试文档处理逻辑和散列逻辑(它应该已经通过自己的单元测试进行了测试)。如果您的测试将输出Result与资源中的某个值进行比较并且它包含哈希值,那么当您将其更改为另一个哈希算法时,此函数的所有单元测试都将被破坏。

  4. 什么是更好的方法?提取一个抽象散列函数的接口,并在需要进行散列时显式请求它。因此,只要它们是无状态的,您仍然可以将算法实现为某种单例,但客户端代码将无法与任何哈希特征相关联。谁知道,你可能有一天会发现你需要一些参数化的哈希算法,你可以在每次需要时创建一个新的算法实例。

    我正在使用你的界面改变了一点风格:

    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
    {
       // 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 ...
       }
    }
    

    它解决了上述问题:

    1. 该类明确声明它依赖于散列。提供特定散列算法的人知道对性能,资源,连接,异常等的期望。

    2. 您可以为每个业务逻辑实例或每个文档配置使用哪种散列算法。

    3. 当您对其进行单元测试时,您可以提供散列的模拟实现,例如始终返回0(并且还检查该方法是否将期望值传递给散列算法)。这是您单独检查散列并单独检查文档处理。

    4. 因此,如果行为有一些变化,那么最重要的是使用标准实例接口。它们背后的代码可以是静态的也可以是非静态的,无关紧要。重要的是,使用可变行为的地方将保持灵活,可扩展和可单元测试。

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