【问题标题】:Dealing with "global" data structures in an object-oriented world在面向对象的世界中处理“全局”数据结构
【发布时间】:2008-10-01 08:29:08
【问题描述】:

这是一个有很多答案的问题 - 我很想知道其他人认为什么是“最佳实践”。

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

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

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

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

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

【问题讨论】:

    标签: oop


    【解决方案1】:

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

    毕竟,在实现 OO 类时,您通常使用操作系统的 API。如果不是一大堆全球数据和服务,这到底是什么鬼!

    如果你在你的程序中使用一些全局的东西,你只是在扩展这个你的类实现已经可以看到操作系统的巨大环境,其中包含一些特定于你的应用程序领域的数据。

    在 OO 课程和书籍中经常教授到处传递指针/引用,从学术上讲,这听起来不错。从务实的角度来看,这通常是应该做的事情,但盲目而绝对地遵循这条规则是被误导的。对于一个体面的程序,您最终可能会得到一堆到处传递的引用,这可能会导致完全不必要的苦差事。

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

    【讨论】:

      【解决方案2】:

      我真的不鼓励您使用选项 3 - 使数据静态化。我参与过几个项目,早期的开发人员将一些核心数据设为静态,后来才意识到他们确实需要运行该程序的两个副本——并且花费了大量的工作使数据成为非静态数据并小心地放入引用进入一切。

      因此,根据我的经验,如果您执行 3),您最终会以两倍的成本执行 1)。

      选择 1,并细化您从每个对象引用的数据结构。不要使用“上下文对象”,只需准确传递所需的数据。是的,它使代码更复杂,但从好的方面来说,它使代码更清晰——FwurzleDigestionListener 持有对FwurzleDigestionTract 的引用的事实立即让读者了解它目的。

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

      【讨论】:

      • 1) 被称为依赖注入,有很多工具可以让它变得非常简单(或者至少不那么麻烦)
      【解决方案3】:

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

      您需要查看应用程序的细节,但总体思路是让一个对象负责共享数据,该对象根据封装在其中的数据为其他对象提供服务。然而,这些服务应该涉及向其他对象提供数据结构 - 只是向其他对象提供他们需要的信息片段以满足他们的职责并在内部对数据结构执行突变。

      【讨论】:

        【解决方案4】:

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

        不要太拘泥于您是纯粹坚持一种编程方法还是另一种,找到适合您问题的解决方案。我认为单身人士有完全有效的上下文(例如记录)。

        【讨论】:

          【解决方案5】:

          我使用了拥有一个全局对象和通过构造函数传入接口的组合。

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

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

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

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

              我们应该注意不要将面向对象的设计与面向对象的实现混淆。 OO 设计这个术语经常被用来判断一个实现,就像,恕我直言,它就在这里。

              设计

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

              从项目的需求中,他可以看到它是否真的需要是一个单例(例如“互联网”),或者该对象是否因为过于通用或过于昂贵或其他原因而被共享。

              实施

              当您被要求用 OO 语言实现 OO 设计时,您会面临很多决定,例如您提到的那个:我应该如何实现 所有箭头 到经常使用的对象设计?

              这就是解决有关“静态成员”、“全局变量”、“上帝类”和“大量函数参数”的问题的地方。

              设计阶段应该已经明确了对象是否需要是单例的。实施阶段将决定如何在程序中表示这种单一性。

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                  【解决方案9】:

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

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

                  【讨论】:

                  • 啊,但是你会遵循什么解决方案呢?您有 20 个类都操作相同的数据结构或对象。您如何授予 20 个班级访问权限?正确地,您必须在构造函数中或使用方法 (1) 将其传递给它们。但这会导致大量重复代码。你是做什么的?
                  • 你的类通过它的构造函数参数告诉世界它需要什么数据——创建它的东西然后将这些数据传递给它
                  • 是否值得在其构造函数中为所有类传递对象?例如,如果您有 20 个类来操作相同的数据结构,您是否更愿意将相同的数据传递给每个类?如果数据结构发生变化会发生什么?
                  • 不要传递上下文对象——你的整个程序依赖于这些对象的细节。遵循得墨忒耳定律——这告诉我们构造函数不应该在上下文对象中寻找他们需要的数据
                  猜你喜欢
                  • 1970-01-01
                  • 2013-02-12
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-10-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-06-01
                  相关资源
                  最近更新 更多