4

C#では、次のような侵入型のツリー構造があります。

public abstract class Node
{
    Container parent;
    Node nextNode;
    Node previousNode;

    public abstract class Container : Node
    {
        Node firstChild;
        Node lastChild;
    }
}

ツリーに追加できるさまざまなオブジェクトは、子を持つことができるかどうかに応じて、Nodeまたは子を継承します。Container

内部クラスを作成することにより、コンテナの子のリストを管理するためにContainerのプライベートメンバーにアクセスできることを意味します。Node

これはすべてうまくいっています。しかし今、私はそれをジェネリックにして、型の安全性を維持しながら再利用できるようにしたいと思っています。基本的に、すべてのツリー機能をNodeの上のジェネリッククラスに移動し、NodeとContainerの間の別のクラスに移動します。これが私がやろうとしていることの大まかなデザインです:

public abstract class GenericNode<Node, Container>
    where Node : GenericNode<Node, Container>
    where Container : GenericNode<Node, Container>.GenericContainer
{
    Container parent;
    Node nextNode;
    Node previousNode;

    public abstract class GenericContainer : Node
    {
        Node firstChild;
        Node lastChild;
    }
}

GenericContainerもちろん、継承元を作成できないため、これは機能しませんNode(コンパイラエラーCS0689)。内部クラスの要件を削除しても(たとえば、internal自分のライブラリを使用して注意するだけで)、同じ問題(およびエラー)が発生しない設計を理解することはできません。

(私はそうしなければならないとは思いませんでしたが、それを詳しく説明します。コンパイルエラーを「修正」しようとはしていません。また、単純なツリー実装を探していません。これはコンテナ設計の質問です。)

そして今、私は少し困惑しています。誰かがこれをどのように設計するかについてもっと良いアイデアを持っていますか?

編集:クラスを継承階層に「注入」する問題を回避するために拡張メソッドを使用しようとする、設計のもう1つの方法であるこの回答を必ず確認してください(ただし、残念ながら完全には機能しません)。

4

6 に答える 6

1

拡張メソッドのアプローチに従って、代わりにインターフェイスで継承制約(NodeとContainerの間)を定義し、インターフェイスでコンテナクラスを装飾するとどうなりますか。

{
    MyNode n = new MyNode();
    var c = new MyNode.MyContainer();
    c.AddChild(n);

    MySubNode s = new MySubNode();
    c.AddChild(s);

    OtherNode o = new OtherNode();
    o.AddChild(o);

    //compiler doesn't allow this, as you'd expect:
    //c.AddChild(o);
}        

public interface IContainer<TContainerType, TNodeType>
    where TNodeType : GenericNode<TContainerType, TNodeType>
    where TContainerType : TNodeType, IContainer<TContainerType, TNodeType>
{
}

public static class ContainerExtensions
{
    public static void AddChild<TContainerType, TNodeType>(this IContainer<TContainerType, TNodeType> self, TNodeType node)
        where TNodeType : GenericNode<TContainerType, TNodeType>
        where TContainerType : TNodeType, IContainer<TContainerType, TNodeType>
    {
        GenericNode<TContainerType, TNodeType>.AddChild(self as TContainerType, node);
    }
}

public class GenericNode<TContainerType, TNodeType>
    where TNodeType : GenericNode<TContainerType, TNodeType>
    where TContainerType : GenericNode<TContainerType, TNodeType>
{
    TContainerType parent;
    TNodeType nextNode;
    TNodeType previousNode;

    // Only used by Container
    TNodeType firstChild;
    TNodeType secondChild;

    internal static void AddChild(TContainerType container, TNodeType node)
    {
        container.firstChild = node;
        node.parent = container;
    }
}

public class MyNode : GenericNode<MyContainer, MyNode>
{        
}

public class MyContainer : MyNode, IContainer<MyContainer, MyNode>
{
}

public class MySubNode : MyNode
{
}

public class OtherNode : GenericNode<OtherNode, OtherNode>, IContainer<OtherNode, OtherNode>
{
}
于 2010-10-02T14:34:28.670 に答える
0

1つのオプションは、Nodeオブジェクトを直接公開しないことにより、ツリーの実際の構造からクライアントを完全に分離することです。

public interface ITagged<T>
{
    T Tag { get; set; }
}

public sealed class Tree<T>
{
    //All Tree operations are performed here (add nodes, remove nodes, possibly move nodes, etc.)
    //Nodes are only exposed as 'ITagged<T>', such as:
    public ITagged<T> Root { get; private set; }

    public IEnumerable<ITagged<T>> GetChildren(ITagged<T> item)
    {
        //Cast to Container and enumerate...
    }

    //Several other tree operations...

    private class Node : ITagged<T>
    {
        Container parent;
        Node nextNode;
        Node previousNode;

        public T Tag { get; set; }
    }

    private class Container : Node
    {
        Node firstChild;
        Node lastChild;
    }
}

ツリーには、特別なタイプから派生したり、ツリー構造を制御するプロパティを含めたりすることなく、任意のタイプのデータオブジェクトを含めることができるようになりました。ツリー構造はすべてTreeクラスによって内部的に処理され、すべてのTree操作はTreeクラスによって提供されます。これで、クライアントは実装の詳細から完全に隔離されます。クライアントが目にするのはデータオブジェクトだけです。それでもノードからのナビゲーションを提供できるようにしたい場合は、ノードインターフェイスでツリーに戻るリンクを提供してから、ツリーメソッドを使用してナビゲーションを実装する拡張メソッドを提供できます。

于 2010-10-02T17:09:33.353 に答える
0

(これを行わないでください-他の誰かが誤って拡張機能を落とすのを防ぐためにそのままにしておきます;))

これは役に立ちますか?

public abstract class GenericNode<Node, Container>
    where Node : GenericNode<Node, Container>
    where Container : GenericNode<Node, Container>.GenericContainer<Node>
{
    Container parent;
    Node nextNode;
    Node previousNode;

    public abstract class GenericContainer<Branch> where Branch: GenericNode<Node, Container> 
    {
        private Leaf firstChild;
        private Leaf secondChild;
    }
}

3.5でもコンパイルします。宣言によりにBranch制限されています。NodeGenericNode

于 2010-10-02T14:51:09.957 に答える
0

私の解決策は次のようになります:

public class Tree<T> : ITree<T> where T : INode{
    public T RootNode { get; private set; }
    public Tree(T rootNode){
        RootNode = rootNode;
    }
}

public interface ITree<T> where T : INode{
    T RootNode { get; }
}

public interface INode{
    INode Parent { get; }
    List<INode> Children { get; }
}

internal class Node : INode{
    public INode Parent { get; private set; }
    public List<INode> Children { get; private set; }
    public Node( INode parent, List<INode> children = new List<INode>()){
        Parent = parent;
        Children = children;
    }
}

HTH。

注:ParentNode!=子ノードの場合はnullなどの追加の検証。ノードは、このサンプルでは実装されていない、追加されているのと同じ親に属しています。

于 2010-10-02T13:49:38.503 に答える
0

実用的な解決策があると思いましたが、完全には機能しません。

public abstract class GenericNode<Node, Container>
    where Node : GenericNode<Node, Container>
    where Container : Node
{
    Container parent;
    Node nextNode;
    Node previousNode;

    // Only used by Container
    Node firstChild;
    Node secondChild;

    public static class ContainerHelpers
    {
        public static void AddChild(Container c, Node n)
        {
            c.firstChild = n; // not a real implementation ;)
            n.parent = c;
        }
    }
}

// EDIT: This does not work correctly! (see example below)
public static class GenericNodeExtensionMethods
{
    public static void AddChild<Node, Container>(this Container c, Node n)
        where Node : GenericNode<Node, Container>
        where Container : Node
    {
        GenericNode<Node, Container>.ContainerHelpers.AddChild(c, n);
    }
}

//
// Example Usage
//

public class MyNode : GenericNode<MyNode, MyContainer>
{
}

public class MyContainer : MyNode
{
}

public class MySubNode : MyNode
{
}

public class OtherNode : GenericNode<OtherNode, OtherNode>
{
}


class Program
{
    static void Main(string[] args)
    {
        MyNode n = new MyNode();
        MyContainer c = new MyContainer();
        c.AddChild(n);

        MySubNode s = new MySubNode();
        //
        // This does not work because it tries to fill the generic in the
        // extension method with <MySubNode, MyContainer>, which does not
        // fulfil the constraint "where Container : Node".
        //
        //c.AddChild(s);

        OtherNode o = new OtherNode();
        o.AddChild(o);
    }
}

Containerのみのメソッドを公開するextensionmethodメソッドは正しく機能しませんが、このようにGenericNodeクラスを構造化すると、ContainerとNodeを同じクラスにすることができるという優れた特性があり、エンドユーザーは特定のタイプを指定できます。子を持つことができるツリー、またはすべてのタイプに子を持つことを許可するツリー。

(また、何らかの理由で、拡張メソッドは、2010年には表示されますが、VC#2008 SP1のIntelliSenseには表示されません。)

まだより良い解決策を探しています...

于 2010-10-02T16:10:17.060 に答える
0

ジェネリック型パラメーター名を修正し、継承されたGenericNodeにジェネリック型パラメーターを追加することを忘れないでください。

すなわち。

public abstract class GenericNode<TNode, TContainer>
    where TNode : GenericNode<TNode, TContainer>
    where TContainer : GenericNode<TNode, TContainer>.GenericContainer
{
    public TContainer Parent { get; set; }
    public TNode Next { get; set; }
    public TNode Previous { get; set; }

    public abstract class GenericContainer : GenericNode<TNode, TContainer>
    {
        public TNode FirstChild { get; set; }
        public TNode LastChild { get; set; }
    }
}

うまくコンパイルします。

于 2010-10-03T18:07:08.357 に答える