分层对象和AutoFixture

gt.*_*ush 5 c# hierarchy circular-reference autofixture

我已经实现了一个用于存储标记的类,标记集合必须是层次化的,所以我的类是:

public class Tag
{
    public int Id { get; set; }
    public int Description { get; set; }
    public Tag ParentTag { get; set; }
    // … (methods for get children, add and remove children, etc.)
}
Run Code Online (Sandbox Code Playgroud)

这样,根标签(用户希望能够拥有许多单独的树)没有父标记,而非根标记必须具有父标记.

  1. 这是实现层次结构的好方法吗?我发现复合模式,但在我的域中,所有标签都只是标签,对于域专家,父标签和子标签之间没有区别.

  2. 在测试中使用AutoFixture会出现问题; 当我需要创建一个简单的标签时,它会引发这个错误:

    失败:: Ploeh.AutoFixture.ObjectCreationExceptionAutoFixture无法创建类型的实例,Ploeh.AutoFixture.Kernel.SeededRequest因为遍历的对象图包含循环引用.

编辑:我阅读使用AutoFixture创建递归树,但它是不同的情况:我只有一个类,而不是2,我不希望自动混合创建一个树,但只有一个节点

sta*_*ica 6

这是实现层次结构的好方法吗?

我看到它有三个问题,一个是小问题,一个是更严重一个问题,一个问题在你的具体情况中显然有问题.

潜在问题:

1.让我们开始与未成年人问题,这是关于属性的名称和类型之间的关系.我建议命名的属性ParentTag应该是Tag类型本身.您声明它的事实int(就像您所做的那样Id)表明您应该调用该属性ParentTagId...或者您将属性的类型更改为Tag.

2.现在到了更严重的问题.我认为它指的Desc是直接的儿童标签.(如果一个标签可以有多个子标签,你显然选择了这个属性的错误类型.你需要某种类型的集合.但这是另一个问题.)

如果您没有得到足够的重视,存储父级和子级链接很容易导致不一致.因此,最好不要为每个标签设置双向链路,而是仅存储一个方向的链路.

然而,这将使在相反方向上遍历层次结构变得复杂.解决这个问题的一种方法是只存储子链接; 如果你想找到T的父标签,你首先要通过递归遍历从根标签开始的层次结构找到T,并持续跟踪你正在采取的"路径"; 然后父节点将成为路径中的倒数第二个标签.

3.现在是最直接的问题.异常提示:

Ploeh.AutoFixture.ObjectCreationException [...]因为遍历的对象图包含循环引用.

使用当前的实现Tag,可以构建包含循环的标记层次结构.我假设你不想那样.

例如,标签C可以将P作为其父标签,尽管P已经是C的子标签.因此,如果你开始跟随ParentTagC开始的链,你将首先到达P然后最终返回到C,如果你继续前进,你会发现自己陷入无限循环.

我不知道AutoFixture,但由于类似的原因,它似乎无法处理您的具体标签层次结构.

你应该使你的标签层次结构有向无环图(DAG) - 这里的"非循环"是重要的一点.但是,使用当前的Tag类,您可以构建任何有向图 ; 它不保证不会有任何循环.

防止循环标记层次结构的方法:

1.ParentTagsetter中实现循环检查:

public Tag ParentTag
{
    …
    set
    {
        if (!IsOrIsAncestorOf(value))
        {
            parentTag = value;
        }
        else
        {
            throw new ArgumentException("ParentTag", "would cause a cycle");
        }
    }
}
private Tag parentTag;

private bool IsOrIsAncestorOf(Tag other)
{
    return this == other || IsOrIsAncestorOf(other.Parent));
    //     ^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //          Is   …   Or    …    IsAncestorOf
}
Run Code Online (Sandbox Code Playgroud)

2.甚至更简单,make ParentTag readonly,它会强制你在构造函数中设置它.这将自动使得无法构建循环标记层次结构 - 如果您不相信它,请尝试它:

public Tag(Tag parentTag)
{
    this.parentTag = parentTag;
}

private readonly Tag parentTag;

public Tag ParentTag
{
    get
    {
        return parentTag;
    }
}
Run Code Online (Sandbox Code Playgroud)

我会推荐第二种解决方案.