Search code examples
c#genericsdesign-patternscomposite

Composite pattern with generic leafs


In my C# .Net 4.0 composite pattern I want to have leafs that are generic. Most examples I found have a generic in the base node which propagates through the whole composite tree. I do not want that.

I have found the following solution (which I've stripped a bit to the essentials). An interface called INode which has two implementations. One called category which basically is a dictionary of INodes. It is a dictionary because I do not want duplicate leafs. The other implementation called ValueNode holds the information. This allows for differently typed leaf nodes.

public interface INode
{
    string Name { get; }
}

public class CategoryNode : INode
{
    public CategoryNode(string name)
    {
        this.Name = name;
        this.Children = new Dictionary<string, INode>();
    }
    public string Name { get; private set; }

    public List<string> Keys
    {
        get { return this.Children.Keys.ToList(); }
    }

    private Dictionary<string, INode> Children { get; set; }

    public INode this[string key]
    {
        get { return this.Children[key]; }
    }

    public void Add(INode node)
    {
        this.Children.Add(node.Name, node);
    }
}

public class ValueNode<T> : INode
{
    public ValueNode(
        string name,
        T defaultValue)
    {
        this.Name = name;
        this.Value = this.Default = defaultValue;
    }

    public ValueNode(
        string name,
        T defaultValue)
    {
        this.Name = name;
        this.Value = this.Default = defaultValue;
    }

    public T Default { get; private set; }

    public T Value { get; set; }

    public string Name { get; private set; }
}

Notice that I've made the children list private so nobody can remove nodes. I am comfortable with this solution. However, the usage syntax it produces is a bit talkative. For example:

((this.root["category"] as CategoryNode)["leaf"] as ValueNode<int>).Value = (node as ValueNode<int>).Value;

While I had envisioned something like

this.root["category"]["leaf"] = node;

Does anybody have ideas for me to simplify the syntax?


Solution

  • How about adding an extension method to INode type ?

    public static class INodeExtensions
    {
        public static void SetValue<T>(this INode node, string key, T v)
        {
            if(v is INode)
            {
                // category node set value
                if(node is CategoryNode)
                {
                    // convert and set value
                }
                else
                {
                    throw new Exception("No children found.");
                }
            }
            else
            {
                // value node set value
            }
        }
    }