分层对象和AutoFixture

5

我已经实现了一个用于存储标签的类,标签集合必须是层次化的,因此我的类如下:

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的递归树,但这是一个不同的情况:我只有一个类,没有两个类,并且我不想使用AutoFixture创建树,而只想创建一个单一节点。

抱歉,我编辑了类并更改了ParentTag类型,int版本是一个错误。Desc属性是用于描述的,我也进行了编辑。 - gt.guybrush
问题更新为答案以重复我的标记,Seemann。 - gt.guybrush
@MarkSeemann,你能删除重复的标签吗?谢谢。 - gt.guybrush
1个回答

6
这是一种好的层次结构实现方式吗?
我看到其中存在三个问题,一个较小,一个稍微严重,一个在您的具体情况下明显有问题。
潜在问题:
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));
    //     ^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //          Is   …   Or    …    IsAncestorOf
}

2. 更简单的方法是使ParentTag为只读,这样你就必须在构造函数中设置它。 这将自动使构建循环标记层次结构变为不可能 - 如果您不相信,请尝试:

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

private readonly Tag parentTag;

public Tag ParentTag
{
    get
    {
        return parentTag;
    }
}

我建议使用第二个解决方案。

而且,当我向构造函数添加参数时,我无法再使用Moq创建一个虚拟对象。 - gt.guybrush
正如您建议的那样,第二种解决方案:简单而简洁。 - gt.guybrush
你写道:“解决这个问题的一种方法是仅存储子链接”,但是这样我怎么移动标签呢?即使现在我必须从私有字段中删除readonly属性才能在Move()方法中更改它。 - gt.guybrush
好的,我已经在这里创建了一个新问题:http://stackoverflow.com/questions/24333693/c-sharp-should-i-have-parent-o-listofchild-property-in-hierarcical-object - gt.guybrush
如果是父级拥有“Child”列表,则编辑父级的该列表,而不是子级的“Parent”属性。(话虽如此,如果这确实是一个新问题,请在SO上提出一个新问题,而不是在此开始后续聊天会话。) - stakx - no longer contributing
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接