这种设计模式有多大意义吗?我最初有一个静态类,它为每个实现的算法返回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#不允许这样做,所以这是我尝试绕过它。
答案 0 :(得分:2)
没有办法伪造静态类接口,很多时候我认为我需要一个实际上需要通常的实例接口。你不能在C#中传递静态类的“实例”,没有办法给函数一个“静态”接口甚至静态“类”来使用它的静态方法。当你调用静态方法时,它总是显式的,你将你的方法“硬链接”到你调用的静态类,这不是一件好事。
基于静态方法的可变性很难进行单元测试。依赖于这种可变性的类不太灵活。想象一下,如果某个函数会从静态类中明确地使用您的算法之一。这样的函数会明确地将其自身耦合到该特定算法。
public class SomeBusinessLogic
{
public Result HandleDocument(IDocument doc)
{
// some transformations...
int hash = Hashing.ReSharperHash.CalculateHash(seed, doc.Properties);
// some other code ...
}
}
嗯,那有什么不对?
该类从未明确声明它依赖于散列。你需要知道它的实现来推理它。在这种情况下,它可能不是很重要,但如果其中一个散列算法非常慢,该怎么办?或者如果它需要磁盘上的一些外部文件?或者,如果它连接到某些外部散列服务?调用HandleDocument
函数时,它可能会意外失败。
如果您想对特定文档使用其他一些散列算法,则无法在不更改代码的情况下执行此操作。
当您进行单元测试时,您可以测试文档处理逻辑和散列逻辑(它应该已经通过自己的单元测试进行了测试)。如果您的测试将输出Result
与资源中的某个值进行比较并且它包含哈希值,那么当您将其更改为另一个哈希算法时,此函数的所有单元测试都将被破坏。
什么是更好的方法?提取一个抽象散列函数的接口,并在需要进行散列时显式请求它。因此,只要它们是无状态的,您仍然可以将算法实现为某种单例,但客户端代码将无法与任何哈希特征相关联。谁知道,你可能有一天会发现你需要一些参数化的哈希算法,你可以在每次需要时创建一个新的算法实例。
我正在使用你的界面改变了一点风格:
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 ...
}
}
它解决了上述问题:
该类明确声明它依赖于散列。提供特定散列算法的人知道对性能,资源,连接,异常等的期望。
您可以为每个业务逻辑实例或每个文档配置使用哪种散列算法。
当您对其进行单元测试时,您可以提供散列的模拟实现,例如始终返回0(并且还检查该方法是否将期望值传递给散列算法)。这是您单独检查散列并单独检查文档处理。
因此,如果行为有一些变化,那么最重要的是使用标准实例接口。它们背后的代码可以是静态的也可以是非静态的,无关紧要。重要的是,使用可变行为的地方将保持灵活,可扩展和可单元测试。
P.S。还有一个方面是“你的域名是什么”。如果您正在编写一些业务应用程序并且在此处和那里调用静态Math.Sqrt(...)
- 那很好,因为没有其他行为。但是,如果您正在编写一些数学库,并且您有几种不同的平方根实现,具有不同的算法或精度,您可能希望将它们包装到接口中并作为接口实例传递以便能够扩展。