在面向对象的世界中处理“全局”数据结构

时间:2008-10-01 08:29:08

标签: oop

这是一个有很多答案的问题 - 我有兴趣知道别人认为是“最佳实践”。

考虑以下情况:您有一个面向对象的程序,它包含许多不同类所需的一个或多个数据结构。如何使这些数据结构可访问?

  1. 您可以显式传递引用,例如,在构造函数中。这是“正确的”解决方案,但它意味着在整个程序中复制参数和实例变量。这使得对全局数据的更改或添加变得困难。

  2. 您可以将所有数据结构放在单个对象中,并传递对该对象的引用。这可以是为此目的创建的对象,也可以是程序的“主要”对象。这简化了(1)的问题,但数据结构可能或可能没有任何关系,并且将它们一起收集在一个对象中是非常随意的。

  3. 您可以使数据结构“静态”。这使您可以直接从其他类引用它们,而无需传递引用。这完全避免了(1)的缺点,但显然不是OO。这也意味着只能有一个程序实例。

  4. 当有很多数据结构时,很多类都需要,我倾向于使用(2)。这是OO纯度和实用性之间的折衷。其他人做什么? (对于它的价值,我主要来自Java世界,但这个讨论适用于任何OO语言。)

9 个答案:

答案 0 :(得分:9)

全球数据并不像许多OO纯粹主义者所声称的那么糟糕!

毕竟,在实现OO类时,您通常会在操作系统中使用API​​。如果它不是一大堆全球数据和服务,那该怎么办呢!

如果您在程序中使用了一些全局内容,那么您只是扩展了这个庞大的环境,您的类实现已经可以通过一些特定于您应用的域的数据看到操作系统。

在任何地方传递指针/参考通常都会在OO课程和书籍中讲授,在学术上听起来不错。实际上,这通常是要做的事情,但盲目而绝对地遵循这一规则是错误的。对于一个体面的大小的程序,你最终会在整个地方传递一堆引用,这可能导致完全不必要的苦差事。

全球可访问的服务/数据提供商(显然是在一个漂亮的界面后面抽象)在一个体面的应用程序中几乎是必须的。

答案 1 :(得分:4)

我必须真的不鼓励你使用选项3 - 使数据保持静态。我曾参与过多个项目,早期的开发人员将一些核心数据静态化,后来发现他们确实需要运行两个程序副本 - 并且需要花费大量的工作才能使数据非静态并仔细地引入参考进入一切。

所以根据我的经验,如果你做3),你最终会以两倍的成本完成1)。

转到1,并对从每个对象引用的数据结构进行细粒度处理。不要使用“上下文对象”,只需准确传递所需的数据。是的,它使代码更复杂,但从好的方面来说,它更清晰 - FwurzleDigestionListener持有对FwurzleDigestionTract的引用的事实立即给出读者了解其目的。

根据定义,如果数据格式发生变化,那么对其进行操作的类也会发生变化,因此无论如何都必须更改它们。

答案 2 :(得分:2)

您可能想要考虑改变许多对象需要了解相同数据结构的要求。似乎没有一种干净的OO共享数据方式的一个原因是共享数据不是非常面向对象的。

您需要查看应用程序的细节,但一般的想法是让一个对象负责共享数据,该对象根据封装在其中的数据为其他对象提供服务。但是,这些服务涉及为其他对象提供数据结构 - 仅向其他对象提供他们需要满足他们的责任并在内部对数据结构执行突变所需的信息。

答案 3 :(得分:1)

我倾向于使用3)并且非常小心跨线程的同步和锁定。我同意它不是OO,但是你承认拥有全球数据,这首先是非OO。

不要过于依赖于是否坚持使用一种编程方法或其他方法,找到适合您问题的解决方案。我认为单身人士有完全有效的背景(例如Logging)。

答案 4 :(得分:1)

我使用了一个全局对象和在via构造函数中传递接口的组合。

从一个主要的全局对象(通常以您的程序调用或命名的名称命名),您可以启动其他全局变量(可能有自己的线程)。这使您可以控制主对象构造函数中程序对象的设置,并在应用程序在此主对象析构函数中停止时以正确的顺序再次将其拆除。直接使用静态类使得初始化/取消初始化这些类以受控方式使用的任何资源变得棘手。这个主要的全局对象还具有用于获取应用程序的不同子系统的接口的属性,各种对象可能希望这些接口可以用来完成它们的工作。

我还将对相关数据结构的引用传递给某些对象的构造函数,我觉得在程序只需要关注它的一小部分时,将这些对象与程序中的其他对象隔离是有用的。

对象是否抓取全局对象并导航其属性以获取它想要的接口,或者通过其构造函数传递它所使用的接口是一个品味和直觉的问题。您正在实现的任何对象,您认为可能会在其他项目中重用,应该通过其构造函数传递应该使用的数据结构。抓取全局对象的对象应该更多地与应用程序的基础结构有关。

接收通过构造函数使用的接口的对象可能更容易进行单元测试,因为您可以为它们提供模拟接口,并勾选它们的方法以确保它们返回正确的参数或正确地与模拟接口交互。要测试访问主要全局对象的对象,您必须模拟主要全局对象,以便当它们从它请求接口(我经常称这些服务)时,它们会获得适当的模拟对象并可以对它们进行测试。

答案 5 :(得分:1)

我更喜欢使用GoF书中描述的单例模式来处理这些情况。单例与问题中描述的三个选项中的任何一个都不相同。构造函数是私有的(或受保护的),因此它不能在任何地方使用。您可以使用get()函数(或您喜欢的任何函数)来获取实例。但是,singleton类的体系结构保证每次调用get()都返回相同的实例。

答案 6 :(得分:1)

我们应该注意不要将面向对象的 设计 与面向对象的 实现 混淆。 Al经常使用术语OO Design来判断实现,就像imho一样,它就在这里。

设计

如果在你的设计中你看到很多物体都提到完全相同的物体,那就意味着很多箭头。设计师应该在这里感到痒。他应该验证这个对象是否只是常用,或者它是否真的是一个实用工具(例如COM工厂,某种注册表......)。

根据项目的要求,他可以看出它是否真的需要是一个单身人士(例如“互联网”),或者如果该对象是共享的,因为它过于笼统或过于昂贵或无论如何。

实施

当你被要求用OO语言实现OO设计时,你面临很多决定,比如你提到的那个:如何将所有箭头实现到经常使用的对象中设计?

这就是关于'静态成员','全局变量','上帝阶级'和'很多功能论点'的问题。

设计阶段应该澄清对象是否需要单身。实施阶段将决定如何在计划中表现这种单一性。

答案 7 :(得分:0)

选项3)虽然不是纯粹的OO,但往往是最合理的解决方案。但我不会让你的班级成为单身人士;并使用其他一些对象作为静态“字典”来管理这些共享资源。

答案 8 :(得分:-2)

我不喜欢你提出的任何解决方案:

  1. 你传递了一堆“上下文”对象 - 使用它们的东西没有指定他们真正感兴趣的字段或数据
  2. 有关God Object模式的说明,请参阅此处。这是世界上最糟糕的世界
  3. 根本不要将Singleton对象用于任何事情。您似乎自己已经发现了一些潜在的问题