这个堆栈溢出问题是关于一个孩子通过指针对其父对象的引用。
最初,评论对于设计是一个可怕的想法至关重要。
我了解这通常不是最好的主意。从一般的经验来看,似乎很公平地说:“不要这样做!”
但是,我想知道在什么样的条件下您将需要执行以下操作。这里的问题和相关的答案/注释甚至建议图不要做这样的事情。
这个堆栈溢出问题是关于一个孩子通过指针对其父对象的引用。
最初,评论对于设计是一个可怕的想法至关重要。
我了解这通常不是最好的主意。从一般的经验来看,似乎很公平地说:“不要这样做!”
但是,我想知道在什么样的条件下您将需要执行以下操作。这里的问题和相关的答案/注释甚至建议图不要做这样的事情。
Answers:
这里的关键不是两个对象是否具有循环引用,而是这些引用是否指示彼此的所有权。
两个对象不能彼此“拥有”:这为初始化和删除顺序造成了棘手的难题。一个必须是可选引用,否则必须指示一个对象将无法管理另一个对象的生存期。
考虑一个双向链接的列表:两个节点来回链接,但是两个节点都不“拥有”另一个(列表同时拥有它们)。这意味着两个节点都不会为另一个节点分配内存,也不会负责另一个节点的身份或生存期管理。
树木具有相似的关系,尽管树中的节点可以分配孩子,而父母确实拥有孩子。从孩子到父母的链接有助于遍历,但同样不能定义所有权。
在大多数OO设计中,将另一个对象作为对象的数据成员进行引用意味着所有权。例如,假设我们有Car和Engine类。两者都不是非常有用的。我们可以说这些对象彼此依赖:它们需要存在另一个对象才能执行有用的工作。但是,另一个“拥有”者呢?在这种情况下,我们可以说汽车拥有引擎,因为汽车是所有汽车零部件所处的“容器”。在OO和现实世界设计中,汽车都是其各个部分的总和,并且所有这些部分在汽车的上下文中连接在一起。引擎可能引用了Car,也可能引用了TorqueConverter,
循环引用可能会产生不良的设计气味,但不一定如此。如果明智地使用并正确记录下来,它们可以使数据结构的使用更加容易。
尝试遍历一棵没有父母和孩子之间双向引用的树。当然,您可以提出一种基于堆栈的方法,该方法既脆弱又复杂,或者可以使用非常简单的基于引用的方法。
这种设计需要考虑几个方面:
类之间的结构依赖性:
如果您打算重用组件类,则应避免不必要的依赖性,并避免使用这种封闭的循环结构。
然而,有时两类在概念上是紧密联系在一起的。在这种情况下,避免依赖不是真正的选择。例如:一棵树及其叶子,或更笼统地说是复合材料及其组件。
对象的所有权:
一个对象拥有另一个吗?或另外说明:如果一个物体被破坏,另一个物体也应被破坏吗?
这个问题由Snowman进行了深入探讨,因此在此不再赘述。
对象之间的导航需求:
最后一个问题是导航需求。让我们以我最喜欢的示例为例,该结构是“ 四个帮”的复合设计模式。
伽玛&al。明确提到可能需要有一个明确的父级引用:“ 保持子组件到父级的引用可以简化遍历和管理复合结构 ”当然,您可以想象系统地进行自顶向下的遍历,但是对于非常大的复合对象,会以指数方式显着降低操作速度。直接参考,甚至圆形也可以大大简化复合材料的操作。
一个例子可以是电子系统的图形模型。复合结构可以代表电子板,电路,元件。要显示和操作模型,您需要在GUI视图中提供一些几何代理。从头到尾进行搜索肯定比从用户选择的GUI元素导航到组件更容易,以找出哪个是父元素,以及相关的兄弟/姐妹元素。
当然,正如Gamma等人指出的那样,您必须确保循环关系的不变性。正如您所提到的SO问题所显示的那样,这可能很棘手。但这是完全可管理且安全的方式。
结论
导航需求不应低估。UML并非毫无道理地在建模符号中明确地对其进行了处理。是的,在完全有效的情况下需要循环引用。
唯一的一点是,有时人们倾向于朝着这样的方向迅速发展。因此,在决定是否要这样做之前,有必要考虑所有涉及的三个方面。
通常,循环引用是一个非常糟糕的主意,因为它们表示循环依赖性。您可能已经知道为什么循环操作不好,但是出于完整性的考虑,tl; dr版本是,每当类A和B都相互依赖时,就无法理解/修复/优化/等A或B,而没有同时了解/修复/优化/其他类。很快就会导致代码库,在这些代码库中,您必须更改所有内容,才能更改任何内容。
但是, 有可能具有循环引用而不会产生有害的循环依赖关系。只要参考在功能上严格是可选的,则此方法有效。就是说,我的意思是您可以轻松地将其从类中删除,即使它们的运行速度较慢,它们仍然可以工作。对于此类循环非依赖创建的引用,我知道的主要用例是能够快速遍历基于节点的数据结构,例如链表,树和堆。例如,原则上,您可以在双向链表上执行的任何操作,也可以在单链链表上执行的操作,恰好有一些操作(例如,在列表中向后移动)具有更好的大操作,带双链接版本的O。
通常这样做不是一个好主意的原因是因为它违反了 Dependency Inversion Principle。人们在此方面写了很多细节,比我在这篇文章中所能介绍的要详细得多,但是归结为它很难维护,因为耦合是如此紧密。更改一个类几乎总是需要对另一个类进行更改,而如果依赖性仅指向一种方式,则接口一侧的更改将被隔离。如果两个类都指向一个抽象接口,那就更好了。
一个主要的例外是,当您在不同的抽象级别上没有两个不同的类,但是没有同一个类的两个节点时,例如,在树中,双向链接列表中,等等。在这里,它更多的是结构关系,而不是结构关系。抽象关系。在这种情况下,出于算法效率的考虑,循环引用是可以接受的,甚至是受鼓励的。
甚至建议图不要做这样的事情。
有时,您只需要以自下而上的方式从不同于树的数据结构访问事物,而树则需要以自上而下的方式访问事物。
例如,四叉树可能会将元素存储在矢量图形软件中。但是,用户的选择存储在矢量元素引用/指针的单独选择列表中。当用户想要删除该选择时,我们必须更新四叉树,并且以自下而上的方式从叶子开始而不是自上而下的方式更新树可能会更有效率。否则,对于每个元素,您都必须从根到叶,然后再次备份。