Я пытаюсь разгадать «Инъекции зависимостей», и я это понимаю, по большей части.
Однако скажите, если по какой-то причине один из моих классов зависел от нескольких классов, а не передавал все это одному классу в конструкторе, есть ли более разумный метод?
Я слышал о DI Containers, так ли я решил решить эту проблему? С чего начать с этого решения? Передаю ли я зависимости от моего DIC, а затем передаю это классу, который нуждается в этих зависимостях?
Любая помощь, которая указала бы мне в правильном направлении, была бы фантастической.
Если у вас есть несколько зависимостей для решения, тогда да, контейнер 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); } }
Люди действительно должны перестать смешивать их. Инъекция зависимостей – это идея, которая исходит из принципа инверсии зависимостей .
DIC – это «волшебное лечение» , которое обещает позволить вам использовать инъекцию зависимостей, но в PHP обычно реализуется путем нарушения любого другого принципа объектно-ориентированного программирования. Худшие реализации, как правило, также присоединяют все это к глобальному состоянию через статический Registry
или Singleton
.
В любом случае, если ваш класс зависит от слишком многих других классов, то в целом это означает недостаток дизайна в самом классе. У вас в основном есть класс со слишком многими причинами для изменения, таким образом, нарушая принцип единой ответственности .
В этом случае контейнер инъекции зависимостей только скроет проблемы с подстилающим дизайном.
Если вы хотите узнать больше о Dependency Injection, я бы рекомендовал вам посмотреть «Чистые коды разговоров» на youtube:
Я написал статью об этой проблеме. Идея состоит в том, чтобы использовать комбинацию абстрактной фабрики и инъекции зависимостей для достижения прозрачной зависимости зависимостей (возможных вложенных) зависимостей. Я буду копировать / вставлять здесь основные фрагменты кода:
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; } }
Инжектором зависимостей является следующее: namespace 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 } }
Контейнер зависимостей является стандартным. Код клиента может выглядеть примерно так:
$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
PS Gica – мое прозвище 🙂
Я рекомендую вам использовать Singltones или Mutlitones. В этих случаях вы всегда сможете получать объекты с помощью методов статического класса.
Другой способ (не может найти правильное имя шаблона, но может быть 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); } }