我正在开发一个遗传算法框架,最初决定了以下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)
中“手动”添加Gene的所有小参数,而不是AddGene(gene)
!
您更喜欢哪种设计?为什么?另外,知道在哪里停止的标准是什么?
我可以对IIndividual
IGene
做同样的事情,因此使用IGene
的任何人都不必知道Interval
。
我有一个Population
课,它将作为IIndividuals
的集合。是什么阻止了我对IIndividual
到Population
做同样的事情?必须有某种边界,某种标准要理解哪些情况最好只是让它(有一些依赖),在哪种情况下最好隐藏它们(就像在第二个IIndividual
实现中一样)。
此外,当实现一个应该由其他人使用的框架时,我觉得第二个设计不那么漂亮(并且其他人可能更难理解)。
谢谢!
答案 0 :(得分:2)
@andersoj让你在正确的轨道上看到了Feather的书,但这里有一些更深入的分析:
Eric Evans 域驱动设计解决了您正在思考的边界类型。 free InfoQ summarization是一个很好的起点,因为埃文斯在我的拙见中过于冗长。埃文斯所描述的聚合根是我认为你需要考虑的边界类型个体看起来像聚合根,暗示Genes和Intervals只存在于Individual类中,并且只能由Individual类创建。假设这些类不需要客户端使用,那么重构Individual类以有效隐藏或删除其他两个类是有道理的。
另一方面,如果客户确实需要独立于个体访问基因和区间,我建议考虑使它们不可变,因此不能隐含地改变个体的状态。
在任何一种情况下,都要从表征测试开始。这是捕获代码正在执行的测试现在。然后从那里重构。
答案 1 :(得分:1)
(我为没有直接回答你的问题而道歉,但我不能不这样指出你......) 我不能高度推荐这本书Working Effectively with Legacy Code(作者Michael Feathers)。对于在(单元,功能)测试下获取代码的挑战,它是一个出色的方法。