分层对象和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类型的实例,因为遍历的对象图包含循环引用。

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

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

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

潜在问题:

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

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

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

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

3.现在是最直接的问题。 exception提示:

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

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

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

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

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

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

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; } } 

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