كيف تصنع فئة شجرة متطفلة في C# استخدم الأدوية الجيرية؟

StackOverflow https://stackoverflow.com/questions/3845966

سؤال

في C# لدي تطفلي هيكل الأشجار الذي يشبه هذا:

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

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

الكائنات المختلفة التي يمكن إضافتها إلى الشجرة ترث من أي منهما Node أو Container اعتمادًا على ما إذا كان يمكن أن يكون لديهم أطفال أم لا.

عن طريق صنع Container الفصل الداخلي ، فهذا يعني أنه يمكنه الوصول إلى الأعضاء الخاصين في Node لإدارة قائمة الحاوية للأطفال.

هذا هو كل شيء حسن وجيد. لكنني الآن أود أن أجعلها عامة حتى أتمكن من إعادة استخدامها مع الحفاظ على سلامة النوع - نقل جميع وظائف الشجرة بشكل أساسي إلى فئة عامة فوق العقدة والآخر بين العقدة والحاوية. هذا تصميم تقريبي لما أحاول القيام به:

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 وفقط حذراً في مكتبتي الخاصة) ، ما زلت لا أستطيع اكتشاف تصميم لا يواجه نفس المشكلة (والخطأ).

(لم أكن أعتقد أنني سأضطر إلى ذلك ، ولكن فقط لتوضيحها: أنا لا أحاول "إصلاح" خطأ التجميع, ، ولا أبحث عن تنفيذ شجرة بسيط. هذا هو سؤال تصميم الحاوية.)

والآن أنا متعثر قليلاً. هل لدى أي شخص أي أفكار أفضل حول كيفية تصميم هذا الشيء؟

تحرير: تأكد من إلقاء نظرة على هذا الجواب, ، وهي طعنة أخرى في التصميم ، تحاول استخدام أساليب التمديد لتجنب مشكلة "حقن" الفصل في التسلسل الهرمي للميراث (لكن للأسف لا يعمل بشكل كامل).

هل كانت مفيدة؟

المحلول

اتباع نهج طريقة التمديد ، ماذا لو حددت قيد الميراث (بين العقدة والحاوية) على واجهة بدلاً من ذلك ، وتزيين فئات الحاويات مع الواجهة.

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

نصائح أخرى

يشبه الحل الخاص بي:

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 للعقد الفرعية ؛ تنتمي العقدة إلى نفس الوالد الذي تتم إضافته وما إلى ذلك. لم يتم تنفيذه في هذه العينة.

(لا تفعل هذا - تركه للمساعدة في منع أي شخص آخر من إسقاط التمديد عن طريق الصدفة أيضًا ؛))

هل هذا يساعد؟

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 يقتصر على كونه أ Node بواسطة GenericNode إعلان.

أحد الخيارات هو عزل العميل من الهيكل الفعلي للشجرة تمامًا ، من خلال عدم تعريض كائنات العقدة مباشرة:

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

يمكن أن تحتوي الشجرة الآن على أي نوع من كائن البيانات ، دون الحاجة إلى النزول من نوع خاص أو تضمين أي خصائص تتحكم في بنية شجرة. تتم معالجة هيكل الأشجار داخليًا من قبل فئة الأشجار ويتم توفير جميع عمليات الأشجار بواسطة فئة الشجرة. الآن العميل معزول تمامًا عن تفاصيل التنفيذ. جميع العميل الذي يرى من أي وقت مضى كائنات البيانات الخاصة بهم. إذا كنت لا تزال ترغب في أن تكون قادرًا على توفير التنقل من العقد ، فيمكنك تقديم رابط إلى الشجرة في واجهة العقدة ثم توفير طرق تمديد تستخدم طرق الشجرة لتنفيذ التنقل.

اعتقدت أن لديّ حل عمل ، لكنه لا يعمل بشكل كامل:

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

على الرغم من أن طريقة طريقة التمديد لفضح الأساليب فقط للحاوية لا تعمل بشكل صحيح ، إلا أن هيكلة فئة GenericNode مثل هذه تحتوي على خاصية لطيفة أن الحاوية والعقدة يمكن أن تكون نفس الفئة - مما يمنح المستخدم النهائي خيار وجود نوع محدد في الشجرة التي يمكن أن تنجب أطفالًا ، أو السماح لجميع الأنواع بإنجاب أطفال.

(أيضًا لسبب ما ، لا تظهر طريقة التمديد في Intellisense في VC# 2008 SP1 ، على الرغم من أنها في عام 2010.)

ما زلت تبحث عن حل أفضل ...

ما عليك سوى إصلاح أسماء المعلمات من النوع العام ، وتذكر إضافة المعلمة من النوع العام إلى 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; }
    }
}

يتجمع على ما يرام.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top