4

I've implemented the composite pattern as follows

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

I've populated the composite from an xml file as follows

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

where

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

This populates my composite as expected - great! However, I'd now like my composite to return an iterator via the implementation of IEnumerable. So I tried this

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

But this only iterates through the top level of components, i.e., any components nested within _children are not returned. How do I update this to recursively iterate through all components?

James B
  • 8,975
  • 13
  • 45
  • 83

2 Answers2

3

You could implement the traversal recursively using Linq as follows.

public IEnumerable<IComponent> GetSuccessors()
{
    return _children
           .Concat(_children.SelectMany(iChild => iChild.GetSuccessors());
}

If depht-first traversal is desired, you can use the following implementation.

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

Or, if you need it using your initial syntax, you could use the following.

public IEnumerator<IComponent> GetEnumerator()
{
    var Successors
        = _children
          .SelectMany(iChild => new IComponent[]{iChild}.Concat(iChild.GetSuccessors()));
    foreach (var iSuccessor in Successors)
    {
        yield return iSuccessor;
    }
}
Codor
  • 17,447
  • 9
  • 29
  • 56
  • This was really informative, and very useful to see different way of approaching the problem (banking this knowledge!). – James B Apr 07 '17 at 11:31
3

You can iterate recursively like this (it will do iteration in depth-first manner):

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

If you want to avoid the cast to IComposite - you need to redesign your interfaces and make your Composite to hold a list of another IComposites instead of components. Then ILeft will also become IComposite with dummy implementation.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • So I changed my interfaces such that `IComponent` implements `IEnumerable`, then used your code above without the cast and in `Leaf` used `yield break` in implementation of `GetEnumerator` (see [here](http://stackoverflow.com/questions/1714351/return-an-empty-ienumerator)). This provides me with depth-first recursion - great! – James B Apr 07 '17 at 11:29