题
想象一下这些关系:
- 1 A 有很多 B
- 1 B 有很多 C...
相反:
- C有1个B
- B有1A
- 根据传递性,C 有 1 个 A
为了在数据库中建模这种关系,我们有:
TableA
a_id
TableB
b_id
a_id (fk to TableA)
TableC
c_id
b_id (fk to TableB)
为了在面向对象中建模这种关系,我们有:
objA
objB
objC
和...-OBJB提到OBJA -OBJC提到OBJB
如果objC有一个方法需要调用objA的方法,你会怎么做?
选项1。
b.getA().runMethodX()
我认识的很多人都会这样做,但我也了解到这不好,因为 getA() 不是纯粹 OO 意义上的 B 行为。这样做就像进行过程编程一样。同意不同意?
选项 2。
通过构造函数注入/setter让objC直接引用objA和objB
这是一个好主意吗?但是 objC 引用的 objB 也引用 objA。这个可以吗?或者只要不是循环对象引用的情况就可以接受?
选项 3。
将有问题的方法移至 objA,并通过参数传入 objC。
我不确定这是否被视为一种选择。我认为这并不是在所有情况下都有效。将 objC 中的方法减少到仅适用于其状态的最低限度,并让 objA 执行 objA 之前或之后需要执行的任何操作。
选项 4。 (代表团)
将 runMethodXinA() 添加到 B,它调用
a.runMethodX()
C 呼叫
b.runMethodXinA()
我之前尝试过这个方法,但是 B 很可能最终会拥有与 A 一样多的方法,并且 B 和 A 中都有 1 个方法是否违反了 DRY?
你又是什么?还有其他选择吗?评论?建议?
解决方案
选项 2 是相当危险的信息重复,此外,根据您的环境,参考图的形状可能会出现任何问题。
如果该方法在某种意义上依赖于 objA 的类型(作为一种双重调度),则选项 3 可能有意义。
选项 1 违反了得墨忒耳定律,但听起来它是干扰性最小的选项。
您可能还需要考虑 objB 上的转发方法,将调用传递给 objA。
其他提示
从您的选择列表中,选择是选项 4,因为它遵循 德墨忒耳定律
- 选项1违反了迪米特定律
- 选项 2 引入了冗余引用 和 间接违反了迪米特法则
- 选项 3 用对象 C 的知识污染了对象 A,这可以说也违反了德米特法则(相反)
剩下选项 4
可能有选项 5,但这超出了原始问题的范围;-)
我认为问题是你想要什么样的模型。如果您要使用提前了解整个结构且不会更改的关系模型,则选项 1 是可以接受的。即A中的数据/方法 在特定的组织模式中 是 C 所必需的。
选项 2 似乎是个好主意。但您本质上是保存对同一对象的两个引用。现在,如果 B 映射到不同的 A,B 也必须通知 C 更改其引用。不好。
然而,选项 4 似乎是实现真正的 OO 方法的正确方法。关键是 A 可以改变它的结构,而你只需要适应 B - 直接子代。C不知道也不关心A是如何实现的,A简单存在并且B知道如何处理它就足够了。
您是否真的需要从 C 到 B 到 A 的后向指针,还是只拥有它们以便可以从 C 访问 A?也许A应该负责创造B和C并管理他们的关系。在这种情况下,A 在创建 C 时可以将自身插入到 C 中(例如C 可能有一个接受 A) 的构造函数。
您还可以隔离 A 上 C 所需的功能并为其创建一个接口。接口(而不是 A)将在构造时传递到 C。这将使 C 与 A 解耦。
不。1 或否。4.视情况而定。在我的金属切割软件中,我们有很多需要知道对象的父级/起源的实例。例如,我们在配件上有一个路径集合,并且每个路径都有一个配件属性,该属性返回创建它的配件。
但是,如果连接对象的存在仅仅是因为它可以帮助原始对象实现其目的(例如数学支持),那么委托可能是更好的方法。
如果连接的对象修改了原始对象,那么您最好使用访问者模式。
唯一需要注意的是,在大多数 OOP 中,您必须小心双向链接的对象。对于具有相互连接的父子链接的对象,垃圾收集通常会失败。忘记清理父子链接是一个常见的错误。
通过支持创建和使用事件的 OOP,您可以解决此问题。通过使用代理。父级将向子级传递一个代理对象而不是其自身。当子对象需要父对象时,它将从代理对象获取引用。代理对象反过来引发一个事件,导致父对象返回自身。没有硬链接,因此减轻了程序员或垃圾收集器不清理的问题。