【问题标题】:PHP Dependency InjectionPHP 依赖注入
【发布时间】:2012-04-08 17:50:46
【问题描述】:

我正在努力理解依赖注入,我理解它,大部分情况下。

但是,假设由于某种原因,我的一个类依赖于多个类,而不是将所有这些类传递给构造函数中的这一类,是否有更好、更明智的方法?

我听说过 DI Containers,我会这样解决这个问题吗?我应该从哪里开始使用这个解决方案?我是否将依赖项传递给我的 DIC,然后将其传递给需要这些依赖项的类?

任何可以为我指明正确方向的帮助都会很棒。

【问题讨论】:

  • "Dependency Injection" is a 25-dollar term for a 5-cent concept -- James Shore

标签: php dependency-injection


【解决方案1】:

依赖注入!== DIC

人们真的应该停止混淆他们。 Dependency Injection 是来自Dependency Inversion principle 的想法。

DIC 是“灵丹妙药”,它承诺让您使用依赖注入,但在 PHP 中通常是通过打破面向对象编程的所有其他原则来实现的。最糟糕的实现也倾向于通过静态RegistrySingleton 将其全部附加到全局状态。

无论如何,如果你的类依赖于太多其他类,那么一般来说,这意味着类本身存在设计缺陷。你基本上有一堂课有太多理由要改变,因此,打破了Single Responsibility principle

在这种情况下,依赖注入容器只会隐藏底层设计问题。

如果您想了解更多关于依赖注入的信息,我建议您观看 youtube 上的“清洁代码讲座”:

【讨论】:

  • 依赖注入是一种实现Ioc的方式对吗?
【解决方案2】:

如果您有多个依赖项需要处理,那么可以使用 DI 容器来解决。

DI 容器可以是由您需要的各种依赖对象构成的对象或数组,这些对象被传递给构造函数并解包。

假设您需要一个配置对象、一个数据库连接和一个传递给每个类的客户端信息对象。您可以创建一个包含它们的数组:

// Assume each is created or accessed as a singleton, however needed...
// This may be created globally at the top of your script, and passed into each newly
// instantiated class
$di_container = array(
  'config' = new Config(),
  'db' = new DB($user, $pass, $db, $whatever),
  'client' = new ClientInfo($clientid)
);

并且您的类构造函数接受 DI 容器作为参数:

class SomeClass {
  private $config;
  private $db;
  private $client;
 
  public function __construct(&$di_container) {
    $this->config = $di_container['config'];
    $this->db = $di_container['db'];
    $this->client = $di_container['client'];
  }
}

您也可以将 DI 容器创建为类本身,并使用单独注入其中的组件类来实例化它,而不是像上面那样创建一个数组(这很简单)。使用对象而不是数组的一个好处是,默认情况下它将通过引用传递给使用它的类,而数组是通过值传递(尽管数组中的对象仍然是引用)。

编辑

在某些方面,对象比数组更灵活,尽管最初的代码更复杂。

容器对象也可以在其构造函数中创建/实例化包含的类(而不是在外部创建它们并传入它们)。这可以为使用它的每个脚本节省一些编码,因为您只需要实例化一个对象(它本身会实例化其他几个对象)。

Class DIContainer {
  public $config;
  public $db;
  public $client;

  // The DI container can build its own member objects
  public function __construct($params....) {
    $this->config = new Config();

    // These vars might be passed in the constructor, or could be constants, or something else
    $this->db = new DB($user, $pass, $db, $whatever);

    // Same here -  the var may come from the constructor, $_SESSION, or somewhere else
    $this->client = new ClientInfo($clientid);
  }
}

【讨论】:

  • @Azirius 我在上面添加了一个对象更灵活的示例。
  • 这是一个相当可怕的 DIC 实现。另外,当您不写入数组时,将数组作为参考传递有什么意义?
  • 你真的会在构造函数中写入一个引用的数组吗?!那太疯狂了。还是只是因为你不了解php5中的copy-on-write ..
  • 不,你不应该。尤其是不像你的柴油那样可怕的东西。
  • 这是服务定位器反模式。谷歌一下。
【解决方案3】:

我已经写了一封article 关于这个问题。 想法是使用抽象工厂和依赖注入的组合来实现(可能嵌套的)依赖的透明依赖解析。我将在这里复制/粘贴主要代码sn-ps:

namespace Gica\Interfaces\Dependency;

interface AbstractFactory
{
    public function createObject($objectClass, $constructorArguments = []);
}

抽象工厂实现是:

namespace Gica\Dependency;

class AbstractFactory implements \Gica\Interfaces\Dependency\AbstractFactory, \Gica\Interfaces\Dependency\WithDependencyInjector
{
    use WithDependencyInjector;

    /**
     * @param string $objectClass
     * @param array $constructorArguments
     * @return object instanceof $class
     */
    public function createObject($objectClass, $constructorArguments =     [])
    {
        $instance = new $objectClass(...$constructorArguments);

        $this->getDependencyInjector()->resolveDependencies($instance);

        return $instance;
    }
}

依赖注入器是这样的: 命名空间 Gica\Dependency;

class DependencyInjector implements \Gica\Interfaces\Dependency\DependencyInjector
{
    use \Gica\Traits\WithDependencyContainer;

    public function resolveDependencies($instance)
    {
        $sm = $this->getDependencyInjectionContainer();

        if ($instance instanceof \Gica\Interfaces\WithAuthenticator) {
            $instance->setAuthenticator($sm->get(\Gica\Interfaces\Authentication\Authenticator::class));
        }
        if ($instance instanceof \Gica\Interfaces\WithPdo) {
            $instance->setPdo($sm->get(\Gica\SqlQuery\Connection::class));
        }

        if ($instance instanceof \Gica\Interfaces\Dependency\WithAbstractFactory) {
            $instance->setAbstractFactory($sm->get(\Gica\Interfaces\Dependency\AbstractFactory::class));
        }
        //... all the dependency declaring interfaces go below
    }
}

dependency container 是标准的。 客户端代码可能如下所示:

$abstractFactory = $container->get(\Gica\Interfaces\Dependency\AbstractFactory::class);

$someHelper = $abstractFactory->createObject(\Web\Helper\SomeHelper::class);

echo $someHelper->helpAction();

注意依赖是隐藏的,我们可以专注于主要业务。我的客户端代码不关心也不知道 $someHelper 需要 Authenticator 或 helpAction 需要 SomeObject 来完成它的工作;

在后台发生了很多事情,检测、解决和注入了很多依赖项。 请注意,我没有使用new 运算符来创建$someObject。实际创建对象的责任交给AbstractFactory

附: Gica 是我的昵称 :)

【讨论】:

    【解决方案4】:

    我建议您使用单音或多音。在这些情况下,您将始终能够通过静态类的方法获取对象。

    另一种方法(找不到正确的模式名称,但可能是Registry)是使用一个全局静态对象来存储多个对象的实例。例如。 (简化代码,没有任何检查):

    class Registry {
        private static $instances = array();
    
        public static function add($k, $v) {
            $this->instances[$k] = $v;
        }
    
        public static function get($k) {
            return $this->instances[$k];
        }
    }
    
    class MyClass {
        public function __construct() {
            Registry::add('myclass', $this);
        }
    }
    

    【讨论】:

    • 我考虑过单例,但是,我读到 DI 是前进的方向。
    • DI 是一个很好的解决方案,但是 IMO 它在 Java(即Automatically injected dependency)中实现得更好,而不是在 PHP 本身中
    • @RepWhoringPeeHaa 解释你的意见。
    • @Botanick 我相信我已经做到了。全局变量很糟糕,而您在代码中拥有的是花哨的全局变量。我建议您观看 teresko 回答中的视频。
    猜你喜欢
    • 2017-02-13
    • 1970-01-01
    • 2010-11-27
    • 2014-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多