我正在开发一个遗传算法框架,最初决定使用以下IIndividual
定义:
public interface IIndividual : ICloneable
{
int NumberOfGenes { get; }
double Fitness { get; }
IGene GetGeneAt(int index);
void AddGene(IGene gene);
void SetGeneAt(int index, IGene gene);
void Mutate();
IIndividual CrossOverWith(IIndividual individual);
void CalculateFitness();
string ToString();
}
看起来还不错,但是一旦我开发了其他使用
IIndividual
的类,我就得出结论,为这些类进行单元测试会很痛苦。为了理解原因,我将向您展示IIndividual
的依赖图:因此,使用
IIndividual
时,我最终还不得不创建/管理IGene
和IInterval
的实例。我可以轻松地解决此问题,将我的
IIndividual
接口重新定义为以下内容:public interface IIndividual : ICloneable
{
int NumberOfGenes { get; }
void AddGene(double value, double minValue, double maxValue);
void SetGeneAt(int index, double value);
double GetGeneMinimumValue(int index);
double GetGeneMaximumValue(int index);
double Fitness { get; }
double GetGeneAt(int index);
void Mutate();
IIndividual CrossOverWith(IIndividual individual);
void CalculateFitness();
string ToString();
}
具有以下依赖关系图:
这将很容易测试,但要以牺牲一些性能为代价(目前我并不担心),并且
IIndividual
较重(更多方法)为代价。对于IIndividual的客户来说,还有一个大问题,就好像他们要添加Gene一样,他们必须在AddGene(value, minimumValue, maximumValue)
中而不是AddGene(gene)
中“手动”添加Gene的所有小参数!我的问题是:
您喜欢哪种设计,为什么?另外,知道在哪里停止的标准是什么?
我可以对
IIndividual
到IGene
做同样的事情,因此使用IGene
的任何人都不必了解Interval
。我有一个类
Population
将用作IIndividuals
的集合。是什么阻止了我对IIndividual
到Population
做同样的事情?必须有某种边界和某种标准,以了解在哪种情况下最好让它存在(具有某些依赖项),在哪种情况下最好将它们隐藏起来(如在第二个IIndividual
实现中)。另外,在实现应该由其他人使用的框架时,我觉得第二个设计不太漂亮(也许其他人很难理解)。
谢谢!
最佳答案
@andersoj让您在正确的方向上关注Feather的书,但是这里有一些更深入的分析:
Eric Evans域驱动设计解决了您正在考虑的边界类型。 free InfoQ summarization是一个很好的起点,因为依我的拙见,埃文斯人太冗长了。埃文斯描述为“聚合根”是我认为需要考虑的边界类型
个体看起来像一个聚合的根,这意味着“基因和间隔”仅针对“个体”类存在,并且只能由“个体”类创建。假定客户端不需要使用这些类,则对Individual类的重构可以有效地隐藏或删除其他两个类。
另一方面,如果客户确实需要独立于个人访问Gene和Interval,我建议考虑使它们不可变,这样就不能隐式更改个人的状态。
无论哪种情况,都从表征测试开始。那是捕获代码现在正在做什么的测试。然后从那里重构。
关于c# - 如何更好地减少类的依赖关系,以便于对其进行单元测试,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4231161/