【问题标题】:How do I avoid passing context object all over the place? [duplicate]如何避免到处传递上下文对象? [复制]
【发布时间】:2011-11-25 22:32:26
【问题描述】:

可能重复:
Dependecy Hell - how does one pass dependencies to deeply nested objects

最近我一直在努力解决这个特殊问题。出于测试和管理的原因,我认为向需要它的人注入像 $config 这样的对象是一个更好的选择。刚开始还可以,后来开始污染代码。 例如: 对象 A 使用对象 B 完成工作,对象 B 使用策略对象 C,对象 C 使用对象 D,这需要 $config 对象。所以,我必须继续在整个链条中传递 $config

在我的代码中,我有两个这样的对象要通过,这使得构造函数很大,有重复的代码,而且通常闻起来有问题。 我将不胜感激在重构这种关系方面的任何帮助。

【问题讨论】:

  • @stereofrog OP 说“我必须继续将 $config 传递到整个链中”,这几乎就是链接问题的标题所要求的,而(我的)接受的答案也在其中回答。

标签: php oop design-patterns


【解决方案1】:

我是否认为 $config 包含...嗯,大部分应用程序所需的配置信息?如果是这样,听起来您应该考虑(普遍存在的)单例设计模式。

如果您还不熟悉它,它是一种在应用程序的整个运行时只允许类的 一个 实例的技术。这在维护应用程序范围的值时非常有用,因为您不会冒实例化第二个对象的风险;你也没有传递对象的“副本”。

例如,检查以下内容:

<?php

class Test {

  private static $instance;

  private function __construct() {  } // Private constructor

  static function instance() {
    if (!isset(self::$instance)) {
       self::$instance = new self();
    }
    return self::$instance;
  }
}

$a = Test::instance();
$b = Test::instance();

$a->property = 'abc';

var_dump($a->property);
var_dump($b->property);

?>

您将看到两个“实例”都包含值为“abc”的“属性”,因为它们实际上都是同一个实例。如果您已经熟悉此技术,我深表歉意,但这听起来确实是您正在寻找的东西!

编辑

如下所述,它仍然可以被克隆。如果你真的想防止这种情况发生,你必须重写魔术方法 __clone() 来阻止这种情况发生。不过,序列化观察只是迂腐。

【讨论】:

  • -1。这不是单例的用例。配置项不必是唯一的并提供全局访问点。在这里使用单例只是拒绝考虑适当的设计。这样做会导致难以维护、难以测试、难以重用和脆弱的代码。见stackoverflow.com/questions/4595964/who-needs-singletons/…
  • 感谢您的回答!至于 Singleton,这是我目前看到的唯一解决方法,但对我来说似乎是一种 hack。正如我在真实场景中所说,我有不止一个这样的对象需要深入嵌套范围,$config 只是其中之一。这就是为什么我正在寻找更通用的解决方案
  • @Gordon - 但在许多情况下,配置对象将是唯一的。 OP 可能没有说太多,但他们肯定没有说与该假设相反的内容。
  • 这正是它必须是唯一的原因。 OP 可能只需要 一个配置,但这并不意味着 OP 可能没有 多个。在这种情况下,当您可以 create once and only once. 时,在这里强制执行唯一性毫无意义
  • 在旁注中,您的单例不是单例,因为它可以被克隆和序列化,从而允许它的多个实例。
【解决方案2】:

而不是(作为一般建议的伪代码)...

config <-- ...

A.constructor (config) {
   this.foo = config.foo
   this.bar = config.bar
   this.objectB = createB (config)
}

B.constructor (config) {
   this.frob = config.frob
   this.objectC = createC (config)
}

C.constructor (config) {
   this.frobnicate = config.frobnicate
   this.objectD = createC (configD)
}

你应该只传递真正需要的东西:

config <-- ...

A.constructor (foo, bar, frob, frobnicate) {
   this.foo = foo
   this.bar = bar
   this.objectB = createB (frob, frobnicate)
}

B.constructor (frob, frobnicate) {
   this.frob = frob
   this.objectC = createC (frobnicate)
}

C.constructor (frobnicate) {
   this.frobnicate = frobnicate
}

让您的州尽可能本地化。全局状态是无数调试恐怖场景的根源(我闻到你刚刚遇到过)。

另外,许多类不必知道它们的对象是什么样子,它们只对公共接口感兴趣。您可以应用依赖倒置,然后:

config <-- ...
objectC = createC (config.frobnicate)
objectB = createB (config.frob, objectC)
objectA = createA (config.foo, config.bar, objectB)

使用依赖倒置意味着你的类不需要知道太多。例如,Car 不需要知道Trailer 及其组成,它只需要知道CouplingDevice

trailer        = createTrailer (...)
couplingDevice = createCouplingDevice (...)

car.install (couplingDevice)

couplingDevice.attach (trailer)

【讨论】:

  • 感谢您的评论!但想法是封装 $config 属性。我不希望对象 A 知道对象 D 需要什么,更不用说我不希望客户知道对象 A 背后的每个对象都需要什么。对象 D 自行决定从 $config 中提取哪些参数。如果对象 D 是一种策略,可以用对象 E 代替,又使用不同的 $config 属性怎么办?
  • 除非上述伪代码代表工厂或建造者,否则合作者不应创建自己的依赖项。此外,在 ctor 中工作是一种代码味道。
  • @Gordon:你怎么不在构造函数中工作?拥有强大的 C++ RAII 背景,我会说相反(当然,除非你使用某种依赖倒置)
  • 请参阅misko.hevery.com/code-reviewers-guide/…,详细了解什么是work。是的,DI 和工厂/建设者是建议的解决方案,以防万一。
  • @Gordon:跳过那篇文章。在我看来,它似乎声明不要将工作投入到合作者身上,而且在我看来,并没有说明工作总体上是一个缺陷。例如,将内存映射和文件系统功能强加给文件对象的用户不是一个好的设计。并且很快就失败了,File-object-construction 的适当位置将是构造函数;缺陷文件不是可用文件,它不应该存在,不存在就是根本不构造。但我同意我们在合作者身上过滤这个论点。
【解决方案3】:

您似乎需要使用singleton 或注册表模式。

单例由一个类(带有私有构造函数)组成,可以通过静态方法创建,以便在每次需要时获取相同的对象(请原谅我的简化)。

它遵循这个方案:

class Config {
    static private instance=null;

    private function __constructor() {
      // do your initializzation here
    }

    static public function getInstance() {
      if (self::instance==null) {
        self::instance== new Config();
      }
      return self::instance;
    }

    // other methods and properties as needed

}

通过这种方式,您可以在需要的地方获得所需的对象,例如

$config = Config::getInstance();

无需将其传递到调用堆栈中而无需使用全局变量。

注册表具有类似的工作方案,但允许您创建一种注册表,因此名称是您需要提供的对象。

【讨论】:

  • 执行Config::getInstance(); 实际上与使用全局变量相同。您假设该类存在于全局范围内,并依赖从中提取的数据实际存在于其中。如果你这样做,你也可以使用global $config。没有区别。
猜你喜欢
  • 2015-03-18
  • 2021-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-29
  • 2015-07-30
  • 2021-10-10
  • 1970-01-01
相关资源
最近更新 更多