分层对象和AutoFixture

时间:2014-06-20 08:45:37

标签: 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.)
}

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

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

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

      

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

  3. 编辑: 我读了Creating recursive tree with AutoFixture,但情况不同:我只有一个课程,而不是2个,我不想自动混合创建一个树而只有一个节点

1 个答案:

答案 0 :(得分:6)

  

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

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

潜在问题:

1。让我们从次要问题开始,这是关于属性名称和类型之间的关系。我建议名为ParentTag的属性本身应为Tag类型。您将其声明为int(就像您对Id所做的那样)这一事实表明您应该调用属性ParentTagId而不是...或者您将属性的类型更改为Tag

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

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

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

3。现在到了最直接的问题。异常提示:

  

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

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

例如,标记 C 可以将 P 作为其父标记,但 P 已经是 C <的子标记< / em>的。因此,如果您从 C 开始关注ParentTag链,您将首先到达 P ,然后最终返回 C ,如果你继续前进,你会发现自己处于无限循环中。

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

你应该使你的标签层次结构directed acyclic graphs (DAGs) - “非循环”在这里是重要的一点。但是,对于您当前的Tag课程,您可以构建任何directed graph;它不能保证不会有任何循环。

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

1。ParentTag setter中实施周期检查:

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
}

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

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

private readonly Tag parentTag;

public Tag ParentTag
{
    get
    {
        return parentTag;
    }
}

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