我实现了一个存储标签的类,标签集合必须是分层的,所以我的类是:
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.)
}
这样根标签(用户希望能够有很多独立的树)没有父标签,而非根标签必须有父标签。
这是实现层次结构的好方法吗?我找到了 Composite Pattern,但在我的领域中,所有标签都是简单的标签,对于领域专家来说,父标签和子标签之间没有区别。
在测试中使用 AutoFixture 时出现问题;当我需要创建一个简单的标签时,它会引发此错误:
Failure:
Ploeh.AutoFixture.ObjectCreationException
: AutoFixture was unable to create an instance of typePloeh.AutoFixture.Kernel.SeededRequest
because the traversed object graph contains a circular reference.
编辑: 我读了Creating recursive tree with AutoFixture但这是不同的情况:我只有一个类,而不是 2 个,我不希望 autofixture 创建一棵树,但只有一个节点
最佳答案
Is this a good way to implement hierarchy?
我看到它有三个问题,一个轻微的,一个稍微严重一点的,还有一个在你的具体情况下明显有问题。
潜在问题:
1. 让我们从小问题开始,这是关于属性名称与其类型之间的关系。我建议名为 ParentTag
的属性本身应该是 Tag
类型。您将其声明为 int
(就像您为 Id
所做的那样)这一事实表明您应该改为调用属性 ParentTagId
... 或者您更改Tag
的属性类型。
2. 现在来谈谈更严重的问题。我认为 Desc
指向一个直接的子标签。 (如果一个标签可以有多个子标签,您显然为此属性选择了错误的类型。您需要某种集合。但这是另一个问题。)
如果您不注意,同时存储父链接和子链接很容易导致不一致。因此,最好不要为每个标签都设置双向链接,而只存储一个方向的链接。
然而,这将使沿相反方向遍历层次结构变得复杂。解决这个问题的一种方法是只存储子链接;如果你想找到 T 的父标签,你首先要通过从根标签开始递归遍历层次结构来找到 T 并不断跟踪“路径” “你正在服用;父级将是路径中的倒数第二个标签。
3. 现在讨论最紧迫的问题。异常提示:
Ploeh.AutoFixture.ObjectCreationException
[…] because the traversed object graph contains a circular reference.
使用您当前的 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;
}
}
我会推荐第二种解决方案。
关于c# - 分层对象和 AutoFixture,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24323300/