Frage

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?

War es hilfreich?

Lösung

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

Andere Tipps

What about using a parameter array to specify the "path" to your leaf? Optionally, there is another method in case you need to get a category node.

class CategoryNode : INode
{
    public CategoryNode GetCategoryNode(params string[] path) {
        CategoryNode cat = (CategoryNode)this.Children[path[0]];
        for (int i = 1; i < path.Length; ++i) {
            cat = (CategoryNode)cat.Children[path[i]];
        }
        return cat;
    }
    public ValueNode<T> GetLeafNode<T>(params string[] path) {
        INode first = this.Children[path[0]];
        if (path.Length == 1 && first is ValueNode<T>) return (ValueNode<T>)first;

        CategoryNode cat = (CategoryNode)first;
        for (int i = 1; i < path.Length - 1; ++i) {
            cat = (CategoryNode)cat.Children[path[i]];
        }
        return (ValueNode<T>)cat.Children[path[path.Length-1]];
    }
}

You use it like this:

var leafNode = root.GetLeafNode<int>("cat1", "cat2", "leaf");
// or
root.GetLeafNode<int>("cat1", "cat2", "leaf").Value = 1234;

The indexer is no longer needed.

I ended up with what Teddy proposed and also added a GetValue. In addition, I put the indexer in the INode interface and just throw an exception when it is called on a value node. This way you can also use the this.root["category"]["leaf"] syntax. You still must cast to a ValueNode<> if you want to access the value property though. But you can do this.root["category1"]["category2"].SetValue<int>("leaf", 42).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top