【问题标题】:How to avoid clashing PHP traits used for dependency injection如何避免冲突用于依赖注入的 PHP 特征
【发布时间】:2014-10-10 20:12:39
【问题描述】:

我终于开始探索 PHP 中的特征了。我想尝试的第一个地方是将配置位注入到类中。如果我使用的是 DIC,我可能在任何需要配置对象的类中都有这样的代码:

protected function SetConfig($config) {
    $this->config = $config;
}

protected $config;

这似乎很适合特征,以避免到处都有样板代码,所以我可能会创建这个:

trait Config {
    protected function SetConfig($config) {
        $this->config = $config;
    }

    protected $config;
}

然后像这样使用它:

class Foo {
    use Config;

    public function __construct() {
        //can now use $this->config
    }
}

那太好了。现在假设我想创建第二个特征,例如,用于日志记录:

trait Logger {
    protected function SetLogger($logger) {
        $this->logger = $logger;
    }

    protected $logger;
}

我可以这样使用:

class Foo {
    use Logger;

    public function __construct() {
        //can now use $this->logger
    }
}

也很棒。现在问题来了,如果这两个特征想要相互使用。一个记录器类需要注入一个配置对象似乎很合理,这意味着这样做:

trait Logger {
    use Config;

    protected function SetLogger($logger) {
        $this->logger = $logger;
    }

    protected $logger;
}

但是当另一个类同时使用这两个特征时,事情就会中断:

class Foo {
    use Config, Logger;

    public function __construct() {
        //want to use $this->config and $this->logger
    }
}

这当然行不通,因为配置位在 Foo 中有效地复制了。

我可以将 use Config; 从 Logger 特征中省略掉,因为我知道它最终会在那里。但这对我来说感觉很奇怪,因为它产生了一种外部依赖。如果我想在没有配置特征的地方使用 Logger 怎么办?此解决方案还意味着我需要让我的 IDE (PhpStorm 8) 警告我有关未知方法的警告,并且不提供自动完成功能。我意识到我可以通过使用@method 依次解决这些问题,但可以这么说,这只是给猪涂口红。

我也可以为 Logger 中的配置位设置别名,但这也是有问题的。

所有这一切都有点味道,但我还没弄清楚这是因为这对我来说是一个新模式,还是真的是一个臭模式。无论哪种方式,我都不确定让这种方法真正发挥作用的最佳方法。

关于在特征中解决这个问题的最佳方法有什么建议吗?还是最好避免 DIC 快捷方式的特征?

【问题讨论】:

  • 我自己从未将traits 用于DI,但我可以提出的一个建议是,如果您有需要其他特征的特征,您可能会受益于将它们转换为类。
  • 一个 logger trait 可能需要配置来知道使用什么日志级别或在哪里写入日志文件。但实际上,问题不仅仅是这个案例——这只是一个例子。更广泛地说,核心功能之间存在依赖关系当然并不少见。在每个类中使用样板代码(属性和设置器)时,这不是问题,但这是特征的问题。在这种情况下,将use Config 添加到 Logger 将不起作用,因为同时使用 Logger 和 Config(上图 Foo)的类将生成致命错误,因为 Config 中的片段重复。
  • 这就是我问这个问题的原因。正如我所说,它有一种奇怪的气味,但与此同时,特征似乎是解决 DI 中固有的样板问题的好方法。那么将两者结合在一起的最佳方法是什么?
  • 我认为“使用 Trait”应该更像 require_once。由于特征无论如何都被压平了,因此忽略重复性并丢弃其中一个并没有什么害处。这种情况下的致命错误对我来说似乎太严重了。

标签: php design-patterns dependency-injection traits


【解决方案1】:

我发现有用的方法是使用 getter 和 setter。然后,这允许您要求存在特定的 getter,而不会与其他特征冲突。

trait Config {
    protected function SetConfig($config) {
        $this->config = $config;
    }

    protected function GetConfig() {
        return $this->config;
    }

    protected $config;
}

trait Logger {
    abstract protected function GetConfig();

    protected function SetLogger($logger) {
        $this->logger = $logger;
    }

    protected $logger;
}

class Baz {
    use Config, Logger;

    // ...

}

在 Baz 中,Config trait 提供了所需的抽象方法,并且 Baz 的组合没有错误。如果您错误地只使用 Logger,您将收到致命错误:Baz 类包含 1 个抽象方法,因此必须声明为抽象或实现其余方法 (Baz::GetConfig)

【讨论】:

  • 这确实完成了工作,尽管它并不理想。让我烦恼的主要事情是:1)文档变得混乱(例如,如果一个人只看到 Logger,怎么知道 GetConfig() 应该来自哪里?) 2)一个人必须使用 Config 才能使用 Logger,这是一个奇怪的外部依赖项,再次难以记录,并且 3)这也与文档有关,但是(can 一个?)如何使用 PHPDoc 正确注释它,以便 IDE 代码完成和重构工作?完成工作的解决方案的要点,但如果有一个真正干净的方法来做到这一点,那就太好了。
  • 作为后续,我意识到保留 1 和 3 可能会在 phpDocumentor 更新中得到解决,以更好地处理特征,如果它们实际上没有得到解决。但是我没有看到解决方案 2。明确要求客户端类知道如果使用 Logger 则必须使用 Config 无论如何都会变得混乱,我认为。我可以看到它在一些明确定义的情况下运行良好,但在这种情况下,Config 和 Logger 对我来说只是感觉它们应该是可重用的“砖块”。再说一次,如果一个类不太可能使用 Logger 而不是 Config....
  • 你知道在traits之间流动的IDE吗?当我单击 $this->getConfig 时,IDE 会转到抽象定义,而不是 trait Config 中的方法定义。也许一个 phpdoc 可以帮助 IDE 做到这一点?
  • 抽象方法只有在组合成实现该方法的类时才有具体实现。可能有多种实现。您将获得的最接近的方法是使用 @see doc 块链接到预期的提供程序(例如,在示例代码中使用 @see Config::GetConfig 注释 Logger 特征的 GetConfig 方法。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-11
相关资源
最近更新 更多