这是一种好的层次结构实现方式吗?
我看到其中存在三个问题,一个较小,一个稍微严重,一个在您的具体情况下明显有问题。
潜在问题:
1.让我们从较小的问题开始,即属性名称和类型之间的关系。我建议名为ParentTag的属性本身应该是Tag类型。您声明它为int(就像Id一样),这表明您应该将属性称为ParentTagId,或者更改属性的类型为Tag。
2.现在来谈更严重的问题。我认为Desc指向直接子标记。(如果一个标记可以有多个子标记,那么您选择了错误的属性类型。您需要某种集合。但这又是另一个问题。)
存储父链接和子链接可能很容易导致不一致,如果您没有注意到这一点。因此,最好不要为每个标记具有双向链接,而只存储单向链接。
然而,这将使在相反方向遍历层次结构变得更加复杂。解决此问题的一种方法是仅存储子链接;如果您想找到T的父标记,则首先通过递归遍历从根标记开始的层次结构,并持续跟踪您正在进行的“路径”;父母将是路径中倒数第二个标记。
3.现在是最紧迫的问题。异常提示了这一点:
“Ploeh.AutoFixture.ObjectCreationException […],因为遍历的对象图包含循环引用。”
使用您当前的Tag实现,可以构建包含循环的标记层次结构。我假设您不希望那样做。
例如,标记C可以将P作为其父标记,尽管P已经是C的子标记。因此,如果您从C开始跟随ParentTag链,您首先会到达P,然后最终回到C,如果您继续走下去,您将发现自己陷入无限循环中。
我不知道AutoFixture,但似乎它不能处理您具体的标记层次结构,原因类似。
您应该将标签层次结构建成“有向无环图(DAG)”——这里的“无环”很重要。但是,使用当前的“Tag”类,您可以构建任何“有向图”,它不能保证不会有任何循环。
防止循环标签层次结构的方法:
1. 在“ParentTag”设置器中实现循环检查;
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));
}
2. 更简单的方法是使ParentTag
为只读,这样你就必须在构造函数中设置它。 这将自动使构建循环标记层次结构变为不可能 - 如果您不相信,请尝试:
public Tag(Tag parentTag)
{
this.parentTag = parentTag;
}
private readonly Tag parentTag;
public Tag ParentTag
{
get
{
return parentTag;
}
}
我建议使用第二个解决方案。