我正在开发一个遗传算法框架,最初决定使用以下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时,我最终还不得不创建/管理IGeneIInterval的实例。
我可以轻松地解决此问题,将我的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的所有小参数!

我的问题是:

您喜欢哪种设计,为什么?另外,知道在哪里停止的标准是什么?

我可以对IIndividualIGene做同样的事情,因此使用IGene的任何人都不必了解Interval
我有一个类Population将用作IIndividuals的集合。是什么阻止了我对IIndividualPopulation做同样的事情?必须有某种边界和某种标准,以了解在哪种情况下最好让它存在(具有某些依赖项),在哪种情况下最好将它们隐藏起来(如在第二个IIndividual实现中)。

另外,在实现应该由其他人使用的框架时,我觉得第二个设计不太漂亮(也许其他人很难理解)。

谢谢!

最佳答案

@andersoj让您在正确的方向上关注Feather的书,但是这里有一些更深入的分析:

Eric Evans域驱动设计解决了您正在考虑的边界类型。 free InfoQ summarization是一个很好的起点,因为依我的拙见,埃文斯人太冗长了。埃文斯描述为“聚合根”是我认为需要考虑的边界类型

个体看起来像一个聚合的根,这意味着“基因和间隔”仅针对“个体”类存在,并且只能由“个体”类创建。假定客户端不需要使用这些类,则对Individual类的重构可以有效地隐藏或删除其他两个类。

另一方面,如果客户确实需要独立于个人访问Gene和Interval,我建议考虑使它们不可变,这样就不能隐式更改个人的状态。

无论哪种情况,都从表征测试开始。那是捕获代码现在正在做什么的测试。然后从那里重构。

关于c# - 如何更好地减少类的依赖关系,以便于对其进行单元测试,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4231161/

10-10 04:16