【问题标题】:Composite pattern with generic leafs具有通用叶子的复合图案
【发布时间】:2013-11-29 15:09:36
【问题描述】:

在我的 C# .Net 4.0 复合模式中,我希望拥有通用的叶子。我发现的大多数示例在基节点中都有一个泛型,它在整个复合树中传播。我不要那个。

我找到了以下解决方案(我已经将其剥离了一些基本要素)。一个名为 INode 的接口,它有两个实现。一个称为类别,基本上是 INodes 的字典。这是一本字典,因为我不想要重复的叶子。另一个名为 ValueNode 的实现保存了这些信息。 这允许不同类型的叶节点。

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

请注意,我已将子列表设为私有,因此没有人可以删除节点。 我对这个解决方案很满意。但是,它产生的使用语法有点健谈。例如:

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

虽然我曾设想过类似的东西

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

有没有人可以帮我简化语法?

【问题讨论】:

  • 你有没有想过 Dictionary>?
  • 您可以从索引器返回dynamic。但是,您将失去类型安全性。但毕竟你并没有真正的类型安全,因为你总是必须转换到正确的类。
  • @rhughes:不,我没有。我没有明确提到一个类别可以有子类别。所以我也希望能够访问 root["category1"]["category2"]["leaf"],这个嵌套字典会帮助我吗?
  • @Nico Schertler:确实有效!缺点是我什至失去了它是一个 INode 的检查。

标签: c# generics design-patterns composite


【解决方案1】:

给INode类型增加一个扩展方法怎么样?

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

【讨论】:

  • 我喜欢这个,因为它让我可以控制类型检查和抛出的异常。它确实使代码更短。我也可以制作一个来获取价值。我现在正在玩弄它。
【解决方案2】:

使用参数数组来指定叶子的“路径”怎么样? 或者,如果您需要获取类别节点,还有另一种方法。

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

你可以这样使用它:

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

不再需要索引器。

【讨论】:

    【解决方案3】:

    我最终得到了 Teddy 的建议,还添加了一个 GetValue。 此外,我将索引器放在 INode 接口中,并且在值节点上调用它时会抛出异常。这样您也可以使用this.root["category"]["leaf"] 语法。 如果您想访问 value 属性,您仍然必须转换为 ValueNode。但是你可以this.root["category1"]["category2"].SetValue&lt;int&gt;("leaf", 42)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-08
      • 1970-01-01
      相关资源
      最近更新 更多