【问题标题】:OO Design Question -- Parent/Child(ren) -- Circular?OO 设计问题 -- 父/子(ren) -- 循环?
【发布时间】:2010-11-04 23:08:49
【问题描述】:

我对 OO 设计过程还很陌生,所以请多多包涵……

我有两个实体需要建模为类,分别称为 Parent 和 Child(它与实际问题域足够接近)。一位父母将有一个或多个孩子——我对这个应用程序,对没有孩子的父母不感兴趣。

我的大脑要去哪里吃午饭是因为我需要能够从另一个中找到任何一个。在我的数据库中,我可以使用正常的外键关系来实现这一点,并且 SQL 的基于集合的性质使得查找给定父级的所有子级或给定子级的父级变得容易。但作为对象...?

我认为父母应该携带一个孩子的集合(列表,随便什么)。我也认为每个孩子都应该引用它的父母。然而,引用的循环性质让我很头疼。

我是:

  • 走在正确的轨道上?
  • 完全脱离基地?如果是这样,我应该采取哪些不同的做法?

这几乎肯定会在 VB.NET 中实现,但我还没有削减代码。

8 个答案后编辑:

谢谢大家。很难只选择一个答案来接受。

澄清答案中被质疑的几件事:

  • 父母和孩子有很大的不同 实体——没有继承 完全的关系。我选择了 我做的名字是因为它们是 真的非常接近现实世界 问题域,现在看到它是 来自 OO 的混淆源 观点。
  • 层次只有一层——孩子永远不会有孩子 在应用程序中。

再次感谢。

【问题讨论】:

  • 孩子也可以有孩子,还是单层等级?如果它是多层次的,那么你需要看看复合模式。
  • 这不只是节点中链接存储的时间与空间问题吗?取决于约束...如果空间不是问题,只要它们具有完整性,存储链接就可以了
  • @Harper Shelby -- 它是单级的。孩子永远不会有自己的孩子(在这个应用程序中)。不过,这是个好问题。

标签: data-structures oop tree class-design


【解决方案1】:

在我看来,您正在走向糟糕的设计。你的架构不应该有循环引用。

您或许应该重新审视一下为什么您的孩子需要参考父母,反之亦然。我会倾向于拥有一群孩子的父母。然后,您可以向父对象添加功能以检查子对象是否是实例的子对象。

更好地解释目标可能也会更有帮助...

编辑

我阅读了更多内容(并听了 cmets)...结果我大错特错了。循环引用实际上确实有它们的位置,只要你小心使用它们并且不要让它们失控。

【讨论】:

  • “你的架构不应该有循环引用。”为什么不?合法的好奇。
  • 理论上,这样的循环引用可能会导致代码无限循环。假设你有代码加载你的父母,然后加载孩子,再次加载父母,再次加载孩子,然后……然后……
  • ORM 工具通常默认为关联生成循环引用。不一定表示设计缺陷。
  • @Justin 什么时候“它可能被不当使用”成为不实施设计的好理由?
  • 好点。在我回答完之后阅读其他几篇文章。我错过了几个有循环引用的好理由。 ...当过度疲劳时,是时候避免这样了。
【解决方案2】:

如果我理解 P 的对象包含一组对象 P->c[] 代表孩子。并且任何没有子节点的节点 P 都是叶子......每个 P 都包含 P->P'(父节点)。

您指定的解决方案是,父母包含对孩子的引用,反之亦然,无需遍历树来获取给定孩子的祖先和节点的孩子。这实际上只是一棵树,您可以在上面执行各种链接和算法来遍历和枚举它。哪个好!

我建议阅读计算机编程艺术中的树一章,以深入了解树结构以及枚举父母和孩子的有效方法。

【讨论】:

    【解决方案3】:

    你说的是类层次结构,父类知道它的子类吗?

    您应该不惜一切代价避免这种情况。

    默认情况下,子类了解父类的所有信息,因为它是父类的实例。但是要让父类知道它的子类需要子类也知道所有其他子类。这会在该类的一个孩子和其他所有孩子之间产生依赖关系。这是一个无法维护的场景, 会在未来引发问题——如果你甚至可以让它编译或运行,而在许多语言中不会出现这种情况。

    也就是说,在我看来,您不是在尝试创建类层次结构,而是在尝试创建集合层次结构,即树。在那种情况下,是的,你走在正确的轨道上。这是一个常见的范例。父节点有子节点的集合,子节点有对父节点的引用。

    问题是什么?他们都是同一类!这是一个非常简单的 C# 示例:

    public class Node
    {
      public readonly Node Parent; // null Parent indicates root node
      public readonly List<Node> Children = new List<Node>();
      public Node(Node parent)
      {
         Parent = parent;
      }
      public Node()
      {
         parent = null;
      }
      public void AddChild(Node node)
      {
         Children.Add(node);
      }
    }
    

    我有一种感觉,这就是你真正想要的。使用此范例,您可以将 Node 子类化为您可能有的任何邪恶目的。

    【讨论】:

    • 对我来说,这听起来像是一种包含而不是继承关系。
    • @Robert:我同意,但提问者不是很清楚,这就是为什么我要求澄清然后编辑。
    • 这些类将彼此不同。孩子(非常)与父母不同。但我可以看出我一开始不太清楚的地方。
    • @RolandTumble:这很好。该范式也适用于异构情况。我担心并且我建议您不惜一切代价避免的问题是 Child 派生自 Parent 而不是 Parent 的 Child 节点的情况。
    【解决方案4】:

    在我看来,你的方向是正确的。根据您的域模型,父母有孩子,孩子有父母。您可能需要互相引用。

    循环引用并没有错,您只需要小心处理它们。当您从数据库加载实体时,您会遇到麻烦以自动方式管理服务器端的实体。例如,您使用查询从数据库中获取 Child 对象。你包括父母信息吗?你包括父母的孩子吗?

    像 Lightspeed 或 Microsoft 的实体框架这样的 ORM 工具通常使用“延迟加载”指令来处理这个问题。他们首先会获取您需要的内容(因此,当您获取 Child 时,它只会获取 Child 属性和父 ID)。如果稍后,您取消对 Parent 的引用,它会去获取 Parent 属性并实例化 Parent 对象。如果稍后,您访问它的 Children 集合,然后它会获取相关的子信息并为该集合创建 Child 对象。但是,在您需要它们之前,它不会填充它。

    【讨论】:

      【解决方案5】:

      我认为希望能够以这种方式遍历对象图是合理的。很难从您的帖子中知道您是否有正当理由,但我认为这些引用本身并不能证明是一个糟糕的设计。

      【讨论】:

        【解决方案6】:

        我相信你在正确的轨道上。为什么引用的循环性质会让你头疼? Parent 引用其子代,Child 引用其父代,您遇到的根本问题是什么?

        【讨论】:

        • 我猜这只是那些年规范化表结构的训练......
        • 啊,是的,我明白了。请记住,在 SQL 世界中,还有一个额外的数据源;包含可以查询的表的实际数据库本身;在数据库级别有一个对那里使用的父级的“前向引用”;在您的班级结构中,没有这样的可查询参考来查找父母。在对象设计中,规范化可能是一个错误,因为无法查询数据;引用必须是明确的。
        【解决方案7】:

        在创建树结构时,循环引用非常好,而且绝对是标准的。例如,HTML 的文档对象模型 (DOM) 在 DOM 树中的每个 node 上都有 parent 和 child 属性:

        interface Node {
            // ...
            readonly attribute Node     parentNode;
            readonly attribute NodeList childNodes;
            // ...
        }
        

        【讨论】:

        • +1 ... 很好的例子,如果循环定义在 some 语言中无关紧要(...在 C 中尝试这个:P ...)由于自动分配跨度>
        • 这不是循环引用。
        • @Randolpho - 不,这是循环定义
        • 是的,这里的许多人都错误地使用了这个词,并增加了混乱。
        • @Randolpho - 取决于语义......当编译器读取它时......它是一个循环定义......它仅适用于解析方法等以及在需求未知时分配空间。对于具有自引用成员 x.y->x 的类 x 的实例。如果 x.y.z->x 则如果算法将遍历对象层次结构,我们就有循环引用。
        【解决方案8】:

        如果子级必须有父级,我通常只需要子级构造函数中的父类型实例。

        【讨论】:

          猜你喜欢
          • 2011-01-16
          • 2011-04-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-04
          • 2010-09-10
          • 1970-01-01
          相关资源
          最近更新 更多