【问题标题】:Easier way to write encapsulated parent/child data structure?编写封装的父/子数据结构的更简单方法?
【发布时间】:2017-07-20 01:32:38
【问题描述】:

有时我发现自己经常写一个“父母”和“孩子”的数据结构,其中:

  • 父级引用了 0 到 N 个不同的子级。
  • 子项引用了 0 个父项或 1 个父项。
  • 引用必须是相互的。对于任何给定的父母,它引用的任何孩子也必须引用给定的父母。对于任何给定的孩子,它引用的父级必须引用给定的孩子。
  • 除了使用反射之外,通过使用可从两个类声明(非private)外部访问的成员来违反上述规则是不可能的。

在实施这样的事情之前可能会采取的心理步骤可能会从这样的事情开始:

public class Parent
{
    private readonly List<Child> _children = new List<Child>();

    public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
}

public class Child
{
    private Parent _parent;

    public Parent Parent
    {
        get
        {
            return _parent;
        }
        set
        {
            if(value == _parent)
                return;

            if(_parent != null)
            {
                _parent._children.Remove(this);
                _parent = null;
            }

            if(value != null)
            {
                value._children.Add(this);
                _parent = value;
            }
        }
    }
}

当然,这不会编译,因为Parent._childrenprivate。但是,您不会想将其设为私有,因为允许在 ChildParent 之外进行访问可能会违反实现或其他地方的规则。

所以,我想出的一个解决方案是将Child 嵌套在Parent 中。嵌套类可以访问其嵌套类的私有成员:

public class Parent
{
    private readonly List<Child> _children = new List<Child>();

    public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();

    public class Child
    {
        private Parent _parent;

        public Parent Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if(value == _parent)
                    return;

                if(_parent != null)
                {
                    _parent._children.Remove(this);
                    _parent = null;
                }

                if(value != null)
                {
                    value._children.Add(this);
                    _parent = value;
                }
            }
        }
    }
}

我要问的问题是,是否有任何替代方法可以实现相同的目标,与这种方法相比,这些方法的缺点更少或更少?我发现这种方法的主要缺点是:

  • 虽然使用partial 会有所帮助,但这可能会导致脚本变大。
  • 这可能会导致比预期更深的嵌套。
  • 这可能导致冗长的类名。要在Parent 之外访问Child,您必须使用Parent.Child。在类名很长的情况下,尤其是在使用泛型时,这可能会导致代码非常难看。
  • 使用泛型(例如,实现编译时类型安全)可能会变得混乱。这似乎至少部分源于 Child 是嵌套的,而 Parent&lt;T1&gt;.Child 是与 Parent&lt;T2&gt;.Child 不同的类型,当您希望类型安全是相互的时,这可能会导致非常难看的泛型使用,或者需要回退到使用运行时强制类型安全(尽管它可以被封装掉,通常,例如,使用非泛型抽象基,其中public 访问器改为protected)。

附带说明,这可能是一个很好的例子,说明使用friend 扩展访问权限可以简化这些问题!

【问题讨论】:

  • 如何在父级 Remove(Child) 中添加一个公共方法,该方法将从列表中删除子级,但在父级内部

标签: c# hierarchy encapsulation friend member-access


【解决方案1】:

我喜欢使用通用的私有接口来公开如下属性:

public class SomeBigOuterClass {
    private interface IChildFriend {
        void SetParent(Parent parent);
    }

    public class Parent {
    }

    public class Child : IChildFriend {
        void IChildFriend.SetParent(Parent parent) {
            this.parent = parent;
        }
        private Parent parent;
    }
}

【讨论】:

  • 有趣,我从未考虑过以这种方式使用接口。请注意,仍然使用嵌套,但随着泛型和继承的类变得更加复杂,它可能更容易管理。
  • 这是我将“朋友”理念带回 C# 的方式,正如您从我的命名约定中看到的那样......
  • Dang,现在我觉得我必须重构使用旧模式的代码:(
  • 我不得不说,当与泛型一起使用时,这种技术实际上在管理类型复杂性方面做得很好。当 Child 嵌套在父类 Parent 上时,将类型约束放在父类上存在您需要解决的问题,而将它们放在 SomeBigOuterClass 上则没有这些问题。 (或者至少,它似乎没有。)非常有帮助,谢谢!
【解决方案2】:

当然,如 cmets 和之前的回答中所述,您还需要封装将子代添加到父代,这需要公共 Add(Child) 方法

【讨论】:

    【解决方案3】:

    正如我在评论中所说,在父级中添加一个 Remove 方法,这至少不会暴露整个子级列表。类似的东西:

    public class Parent
        {
            private readonly List<Child> _children = new List<Child>();
    
            public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
    
            public void Remove(Child child)
            {
                if(child !=null) 
                {
                 _children.Remove(child);
                }
            }
        }
    

    【讨论】:

    • 这将允许打破相互引用的要求。可以删除一个孩子,但孩子仍然会引用父母。
    • 我想孩子和父母都可以有 Add/Remove 方法,这些方法有条件地根据第一个被调用的方法相互调用(这可以通过检查父引用或子引用来确定)虽然我可以看到这变得难以理解并且容易出现无限递归。
    • 不会无限递归,不为null就删除。所以删除父级,意味着如果它不为空,如果它为空,你就不会这样做。所以没有无限递归
    • 我确信这是可行的,但是当您考虑到单个交易可能涉及三个实体(两个父母和一个孩子)时,它会变得有点冒险。
    猜你喜欢
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    • 2022-08-23
    • 1970-01-01
    • 2015-06-23
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多