关于c#:为复合模式的叶节点实现IEnumerable

Implementing IEnumerable for Leaf Nodes of Composite Pattern

我已经实现了如下的复合模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public interface IComponent
{
   string Name { get; }
}

public interface IComposite : IComponent
{
    void AddRange(IEnumerable<IComponent> components);
}
public interface ILeaf : IComponent
{
    string Content { get; }
    string Parent { get; }
}

public class Composite : IComposite
{
    // return an iterator?
    private readonly List<IComponent> _children = new List<IComponent>();

    public Composite(string name)
    {
        Name = name;
    }

    public string Name { get; }

    public void AddRange(IEnumerable<IComponent> components)
    {
        _children.AddRange(components);
    }
}

public class Leaf : ILeaf
{
    public string Name { get; }
    public string Content { get; }
    public string Parent { get; }

    public Leaf(string name, string content, string parent)
    {
        Name = name;
        Content = content;
        Parent = parent;
    }
}

我已经从一个XML文件中填充了组合,如下所示

1
2
var collection = XElement.Load(@"C:\somexml.xml");
   var composite = CreateComposite(collection);

哪里

1
2
3
4
5
6
7
8
9
public IComponent CreateComposite(XElement element)
    {
        if (!element.HasElements)
            return new Leaf(element.Name.LocalName, element.Value, element.Parent.Name.LocalName);

        var composite = new Composite(element.Name.LocalName);
        composite.AddRange(element.Elements().Select(CreateComposite));
        return composite;
    }

这会按预期填充我的合成图-太好了!但是,现在我希望复合语句通过IEnumerable的实现返回一个迭代器。所以我尝试了这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Composite : IComposite, IEnumerable<IComponent>
{
    // return an iterator?
    private readonly List<IComponent> _children = new List<IComponent>();

    public Composite(string name)
    {
        Name = name;
    }

    public string Name { get; }

    public void AddRange(IEnumerable<IComponent> components)
    {
        _children.AddRange(components);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<IComponent> GetEnumerator()
    {
        foreach (var child in _children)
        {
            yield return child;
        }
    }
}

但这只会迭代组件的顶层,即不会返回嵌套在_children中的任何组件。如何更新它以递归方式遍历所有组件?


您可以像这样递归迭代(它将以深度优先的方式进行迭代):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IComposite : IComponent, IEnumerable<IComponent>
{
    void AddRange(IEnumerable<IComponent> components);
}

public IEnumerator<IComponent> GetEnumerator()
{
    foreach (var child in _children)
    {
        yield return child;
        var composite = child as IComposite;
        if (composite != null) {
            foreach (var sub in composite) {
                yield return sub;
            }
        }
     }
}

如果您想避免强制转换到IComposite,您需要重新设计您的接口,并使您的Composite保存另一个IComposite的列表,而不是组件。然后,ILeft也将成为IComposite,采用虚拟实现。


您可以使用linq递归实现遍历,如下所示。

1
2
3
4
5
public IEnumerable<IComponent> GetSuccessors()
{
    return _children
           .Concat(_children.SelectMany(iChild => iChild.GetSuccessors());
}

如果需要depht first遍历,可以使用以下实现。

1
2
3
4
5
public IEnumerable<IComponent> GetSuccessors()
{
    return _children
           .SelectMany(iChild => new IComponent[]{iChild}.Concat(iChild.GetSuccessors()));
}

或者,如果您需要使用初始语法,您可以使用以下内容。

1
2
3
4
5
6
7
8
9
10
public IEnumerator<IComponent> GetEnumerator()
{
    var Successors
        = _children
          .SelectMany(iChild => new IComponent[]{iChild}.Concat(iChild.GetSuccessors()));
    foreach (var iSuccessor in Successors)
    {
        yield return iSuccessor;
    }
}