【问题标题】:How to solve cross referencess in OOP?如何解决 OOP 中的交叉引用?
【发布时间】:2009-03-05 06:45:30
【问题描述】:

我现在遇到过几次,我想知道解决循环引用的 OO 方法是什么。我的意思是 A 类有 B 类作为成员,而 B 又具有 A 类作为成员。

这方面的一个例子是类 Person,它的成员是 Person 配偶。

Person jack = new Person("Jack");
Person jill = new Person("Jill");
jack.setSpouse(jill);
jill.setSpouse(jack);

另一个例子是具有一些其他产品集合作为成员的产品类。例如,该集合可能是对该产品感兴趣的人也可能感兴趣的产品,并且我们希望在每个产品基础上维护该列表,而不是基于相同的共享属性(例如,我们不想只显示同一类别中的所有其他产品)。

Product pc = new Product("pc");
Product monitor = new Product("monitor");
Product tv = new Product("tv");
pc.setSeeAlso({monitor, tv});
monitor.setSeeAlso({pc});
tv.setSeeAlso(null);

(这些产品只是为了说明问题,问题不在于某些产品是否会相互关联)

一般来说,这在 OOP 中会是糟糕的设计吗?所有 OOP 语言都会/应该允许这样做,还是只是不好的做法?如果这是不好的做法,解决这个问题的最佳方法是什么?

【问题讨论】:

  • @Jon Limjap:感谢您纠正错字。

标签: oop


【解决方案1】:

您提供的示例(无论如何对我而言)是合理的 OO 设计示例。

您描述的交叉引用问题不是任何设计过程的产物,而是您作为对象表示的事物的现实特征,所以我认为没有问题。

您遇到过什么让您觉得这种方法设计不佳的情况?

3 月 11 日更新:

在缺乏垃圾收集的系统中,内存管理是显式管理的,一种常见的方法是要求所有对象都有一个所有者 - 一些其他对象负责管理该对象的生命周期。

一个例子是Delphi的TComponent类,它提供了级联支持——销毁父组件,所有拥有的组件也被销毁。

如果您正在开发这样一个系统,那么这个问题中描述的引用循环类型可能被认为是糟糕的设计,因为没有明确的所有者,也没有一个对象负责管理生命周期。 p>

我在某些系统中看到的处理方式是保留引用(因为它们正确地捕获了业务关注点),并添加了一个明确的 TransactionContext 对象,该对象拥有加载到的所有内容数据库中的业务域。此上下文对象负责了解需要保存哪些对象,并在处理完成时清理所有内容。

【讨论】:

  • 这对我来说似乎也是现实生活,我只是在想虚拟机不会因为追逐对象而感到困惑......加载一个人,加载那个人的配偶,再次加载那个人的配偶,......这是一个永恒的循环。只是想知道是否所有虚拟机都可以正确处理。
  • 虚拟机?你的意思是运行时?只有引用计数的系统(例如 COM 或滚动您自己的智能指针)会,任何真正的 GC 系统都不会
  • CLR (.NET) 和 JVM 环境都能很好地处理引用周期。早期的 Lisp 和 Smalltalk 环境也很好地处理了它们,其中大部分技术最初都得到了证明。
【解决方案2】:

这不是面向对象设计的基本问题。它可能成为问题的一个例子是在图遍历中,例如,找到两个对象之间的最短路径——你可能会陷入无限循环。但是,这是您必须根据具体情况考虑的事情。如果您知道在这种情况下可能存在交叉引用,则编写一些检查代码以避免无限循环(例如,维护一组已访问节点以避免重新访问)。但是,如果没有理由它可能是一个问题(例如在您在问题中给出的示例中),那么拥有这样的交叉引用一点也不坏。正如您所描述的,在许多情况下,它是解决手头问题的好方法。

【讨论】:

    【解决方案3】:

    我不认为这是一个交叉引用的例子。

    交叉引用通常与这种情况有关:

    class A
    {
        public void MethodA(B objectB)
        {
           objectB.SomeMethodInB();
        }
    }
    
    class B
    {
        public void MethodB(A objectA)
        {
            objectA.SomeMethodInA();
        }
    }
    

    在这种情况下,每个对象都可以相互“接触”; A 调用 B,B 调用 A,它们变得紧密耦合。如果 A 和 B 在不同的包/命名空间/程序集中,情况会更糟;在许多情况下,由于程序集是线性编译的,因此会产生编译时错误。

    解决这个问题的方法是让任一对象实现具有所需方法的接口。

    在您的情况下,您只有一个级别的“进入”:

    public Class Person
    {
        public void setSpouse(Person person)
        { ... }
    }
    

    我不认为这是不合理的,甚至不是交叉引用/循环引用的情况。

    【讨论】:

      【解决方案4】:

      这是一个问题的主要原因是如果它变得过于混乱而无法处理或维护,因为它可能成为一种意大利面条代码。

      但是,谈谈你的例子;

      See Also 是完全有效的如果这是您代码中需要的功能 - 它是指向用户可能感兴趣的其他项目的指针(或引用)的简单列表。

      同样,添加配偶也是完全有效的,因为这是一个简单现实世界的关系,不会让维护你的代码的人感到困惑。

      我一直认为这是一种潜在的代码气味,或者可能是一种警告,让我退后一步,合理化我的工作。

      至于一些在你的代码中找到递归关系的系统(在上面的评论中提到),无论这种设计如何,这些都可能出现。我最近研究了一个元数据捕获系统,该系统具有递归“类型”的关系——即列在逻辑上与其他列相关。它需要由试图解析您的系统的代码来处理。

      【讨论】:

        【解决方案5】:

        我认为循环引用本身不是问题。

        但是,将所有这些关系放在对象中可能会增加太多混乱,因此您可能希望在外部表示它们。例如。您可以使用哈希表来存储产品之间的关系。

        【讨论】:

          【解决方案6】:

          引用其他对象根本不是一个真正糟糕的 OO 设计。这是在每个对象中管理状态的方式。

          一个好的经验法则是得墨忒耳法则。看看这张完美的 LoD 论文(Paperboy and the wallet):click here

          【讨论】:

            【解决方案7】:

            解决此问题的一种方法是通过 id 引用其他对象。

            例如

            Person jack = new Person(new PersonId("Jack"));
            Person jill = new Person(new PersonId("Jill"));
            jack.setSpouse(jill.getId());
            jill.setSpouse(jack.getId());
            

            我并不是说这是一个完美的解决方案,但它会阻止循环引用。您正在使用对象而不是对象引用来为关系建模。

            【讨论】:

            • 这太像数据库了。设置对对象的引用可以用更少的工作完成同样的事情。
            • 但是如果你想将对象图持久化到数据库中,这种方法将允许在没有代理的情况下部分加载对象图并防止循环引用。正如我所说,它不是完美的解决方案,但我已经看到它成功地用于数百万行代码的应用程序中。
            猜你喜欢
            • 1970-01-01
            • 2013-05-08
            • 2016-02-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-04-24
            • 1970-01-01
            • 2013-08-27
            相关资源
            最近更新 更多