通过多个层传递实例是不好的做法吗?


60

在程序设计中,我经常遇到必须将对象实例传递给多个类的情况。例如,如果我有一个加载音频文件的控制器,然后将其传递给播放器,然后播放器将其传递给playerRunnable,后者又将其传递到其他地方,等等。这看起来很糟糕,但我没有知道如何避免。还是可以这样做?

编辑:也许播放器示例不是最好的,因为我以后可以加载文件,但是在其他情况下不起作用。

Answers:


54

正如其他人提到的那样,这不一定是一个不好的做法,但是您应该注意,不要打破各层关注点的分离,并在各层之间传递特定于层的实例。例如:

  • 数据库对象绝不应传递到更高的层。我见过使用.NET的 DataAdapter类(一种DB访问类)并将其传递到UI层的程序,而不是在DAL中使用DataAdapter来创建DTO或数据集并将其传递的程序。DB访问是DAL的域。
  • UI对象当然应该限于UI层。再一次,我看到这是违反的,既有填充有传递到BL层的用户数据的ListBox,而不是其内容的数组/ DTO,还有(我的特别喜欢的)DAL类,它从中检索层次结构数据DB,而不是返回分层数据结构,而是创建并填充了TreeView对象,然后将其传递回UI以动态添加到表单中。

但是,如果您要传递的实例是DTO或实体本身,则可能没问题。


1
这听起来可能令人震惊,但是在.NET的黑暗初期,这是通常推荐的做法,并且可能比大多数其他堆栈所进行的工作要好。
Wyatt Barnett 2012年

1
我不同意。的确,Microsoft认可了单层应用程序的实践,其中Winforms客户端也访问了数据库,并且将DataAdapter作为不可见的控件直接添加到表单中,但这只是特定的体系结构,与OP的N层设置不同。但是,在多层体系结构中,甚至在.NET之前,对于VB6 / DNA都是如此,DB对象仍停留在DB层中。
Avner Shahar-Kashtan 2012年

需要说明的是:您是否看到人们从其“数据层”直接访问UI(在您的示例中为列表框)?我不认为我在生产代码中遇到过这样的违规..哇..
Simon Whitehead

7
确实是@SimonWhitehead。不清楚区分的人在ListBox和数组之间,并使用ListBoxes作为DTO。这一刻帮助我认识到我做出了许多看不见的假设,而这些假设对其他人来说并不直观。
Avner Shahar-Kashtan 2012年

1
@SimonWhitehead-是的,确实,我已经在VB6和VB.NET Framework 1.1和2.0程序中看到了它们,并被要求维护此类怪物。它变得非常丑陋,很快。
jfrankcarr 2012年

15

有趣的是,还没有人谈论过不可变对象。我认为,将不可变的对象遍历所有各个层实际上是一件好事,而不是为每个层创建很多短暂的对象。

埃里克·利珀特(Eric Lippert)在他的博客上对不变性进行了一些精彩的讨论

另一方面,我认为在层之间传递可变对象是不好的设计。您实质上是在构建一个层,并承诺周围的层不会以破坏代码的方式对其进行更改。


13

传递对象实例是正常的事情。它减少了保持状态(即实例变量)的需要,并使代码与其执行上下文脱钩。

您可能面临的一个问题是重构,这是当您必须更改调用链中多个方法的签名以响应该链底部附近某个方法的参数要求变化时。但是,可以使用有助于重构的现代软件开发工具来缓解这种情况。


6
我想说,您可能面临的另一个问题是不变性。我可以记得我在一个项目中遇到的一个非常令人困惑的错误,在该项目中,开发人员修改了DTO,却没有想到他的特定类并不是唯一引用该对象的类。
Phil

8

可能很小,但是有可能在层之一中的某个位置分配此引用,此后可能导致悬挂的引用或内存泄漏。


您的观点是正确的,但是从OP的术语(“传递对象实例”)来看,我觉得他要么传递值(而不是指针),要么他在谈论垃圾收集的环境(Java,C#,Python,Go 、。 ..)。
Mohammad Dehghan

7

如果只是因为在代码的远程区域中需要对象而传递对象,则使用控制反转和依赖注入设计模式以及可选的适当IoC容器可以很好地解决有关携带对象实例的问题。我已经在一个中等规模的项目上使用它,并且永远不会再考虑不使用它而编写大量服务器代码。


听起来很有趣,我已经使用了构造函数注入,并且我认为我的高级组件控制着低级组件。您如何使用IOC容器来避免随身携带实例?
Puckl 2012年

我想我在这里找到了答案:martinfowler.com/articles/injection.html
Puckl 2012年

1
旁注,如果您使用Java,Guice确实很不错,并且可以将绑定范围限制到诸如请求之类的东西,以便高级组件在此时创建范围并将实例绑定到正确的类。
戴夫

4

通过一堆层的数据传递不是一件坏事,这实际上是分层系统可以工作而不破坏分层结构的唯一方法。出现问题的迹象是,当您将数据传递到同一层中的多个对象以实现目标时。


3

快速解答:什么不对的传递对象的实例。还要提到的是,要跳过在所有层中分配此参考的操作,这可能会导致悬挂的参考或内存泄漏。

在我们的项目中,我们确实使用这种做法在层之间传递DTO(数据传输对象),这是非常有用的做法。我们还重复使用dto对象来构造一次更复杂的对象,例如摘要信息。


3

我主要是一名Web UI开发人员,但对我来说,您的直觉不适可能与实例传递无关,而更多地是您对该控制器使用了一些程序这一事实。您的控制器是否应该掌握所有这些详细信息?为什么要播放音频甚至引用多个对象的名字呢?

在OOP设计中,我倾向于考虑常绿的东西和更可能发生变化的东西。变更内容的主题是您倾向于倾向于将其放置在较大的对象框中,以便即使玩家进行更改或添加新选项时也可以保持一致的界面。或者,您发现自己想大量交换音频对象或组件。

在这种情况下,您的控制器需要确定需要播放音频文件,然后以一致/常绿的方式播放音频文件。另一方面,随着技术和平台的改变或添加新的选择,音频播放器的内容很容易更改。所有这些细节都应位于更大的复合对象IMO的接口下,并且当音频播放方式的细节发生变化时,您不必重写控制器。然后,当您将具有文件位置等详细信息的对象实例传递到较大的对象中时,所有交换工作都是在适当的上下文内部完成的,而上下文中某人不太可能对它进行愚蠢的操作。

因此,在这种情况下,我不认为对象实例被弄乱可能会困扰您。是Picard队长跑到引擎室打开经纱芯,再跑回到桥上以绘制坐标,然后在打开盾牌后点击“ punch-it”按钮,而不是简单地说“ Take我们在第9扭曲处到达X行星。做到这一点。” 让他的工作人员整理细节。因为当他以这种方式处理时,他可以在舰队中担任任何一艘船的船长,而无需知道每艘船的布局以及一切运作方式。这最终是IMO赢得的最大的OOP设计大奖。


2

尽管您的应用程序对此类事情很敏感,但您可能会遇到延迟问题,这是一个很常见的设计,但最终还是会发生。


2

如果您的语言具有动态范围的变量或线程本地存储,则可以使用动态范围的变量解决此问题。这些机制使我们可以将一些自定义变量与激活链或控制线程相关联,这样我们就不必将这些值传递到与它们无关的代码中,从而可以将它们传递给其他代码。确实需要它们。


2

正如其他答案所指出的那样,这并不是天生的糟糕设计。它可以在嵌套类和嵌套类之间建立紧密的耦合,但是如果嵌套引用为设计提供了一个值,则松开耦合可能不是有效的选择。

一种可能的解决方案是“拉平”控制器类中的嵌套引用。

您可以在控制器类中维护对所有嵌套对象的引用,而不是通过嵌套对象多次传递参数。

具体实现的方式(甚至是有效的解决方案)取决于系统的当前设计,例如:

  • 您是否能够在控制器中维护某种嵌套对象的映射而又不会变得太复杂呢?
  • 当您将参数传递给适当的嵌套对象时,嵌套对象可以立即识别出该参数,还是在将参数传递给嵌套对象时发生了其他功能?
  • 等等

这是我在GXT客户端的MVC设计模式中遇到的一个问题。我们的GUI组件包含多层嵌套的GUI组件。更新模型数据后,我们最终将其传递到多个层,直到到达适当的组件为止。它在GUI组件之间造成了不必要的耦合,因为如果我们希望新的GUI组件类接受模型数据,则必须创建方法来更新包含新类的所有GUI组件中的模型数据。

为了解决这个问题,我们在View类中维护了对所有嵌套GUI组件的引用映射,以便每当更新模型数据时,View都可以将更新后的模型数据直接发送到需要它的GUI组件,这是故事的结尾。 。这很好,因为每个GUI组件只有一个实例。如果某些GUI组件有多个实例,我会发现它不能很好地工作,这使得很难确定需要更新哪个副本。


0

您所描述的是所谓的责任链设计模式。苹果公司将这种模式用于其事件处理系统,这是值得的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.