Зависимость-инъекция в реальной жизни - PullRequest
20 голосов
/ 19 февраля 2011

Я создаю действительно минимальную среду MVC, чтобы расширить свои знания PHP и испытать себя. Я пришел к тому, что классы начинают зависеть друг от друга в работе. Внедрение зависимостей кажется решением этой проблемы и используется некоторыми из больших фреймворков .

Я нашел Bucket на Github и некоторое время обдумывал его, чтобы понять основы. Что я не могу понять, но когда уместно создать Контейнер?

Создание одного большого контейнера, включающего все возможные классы, которые могут понадобиться , кажется мне ничем иным, как контрпродуктивным, и я не могу представить, что это хорошая практика. Похоже, рецепт плохой работы по крайней мере.

В альтернативе, которая заключается в создании нескольких контейнеров, я до сих пор не понимаю, почему вонючие синглеты больше не нужны.

Допустим, у меня был бы следующий код:

$session_container = new bucket_Container();
$session_container->create('Database');
$session_container->create('Database_Sessions');

$log_container = new bucket_Container();
$log_container->create('Database');
$log_container->create('Database_Log');

Таким образом, здесь у нас есть два контейнера, или в данном случае корзины для двух совершенно разных типов использования, которые взаимны в зависимости от класса Database.

Моя логика говорит мне, что приведенный выше код создаст два независимых экземпляра Database -класса, а это означает, что мне все равно придется сделать Database -класс одиночным, чтобы гарантировать, что одновременные экземпляры моего соединения с базой данных не будут не происходит?

Это правильно?

Ответы [ 3 ]

9 голосов
/ 19 февраля 2011

Я не знаю много о конкретной библиотеке, но при условии, что она позволяет вам использовать фабрику, пусть фабрика возвращает тот же экземпляр.

Редактировать: Хорошо, это просто на странице индекса Bucket GitHub.

class MyFactory {
  function new_PDO($container) {
    return new PDO("mysql:host=localhost;dbname=addressbook", "root", "secret");
  }
}

$bucket = new bucket_Container(new MyFactory());
$db = $bucket->get('pdo');

Так что в вашем случае вы можете просто сделать:

class MyFactory {
   private $pdo;
   function new_Database($container) {
     if($this->pdo){
         return $this->pdo;
     }
     return $this->pdo = new PDO("mysql:host=localhost;dbname=addressbook", "root", "secret");
   }
}
$factory = new MyFactory();

$session_container = new bucket_Container($factory);
$session_container->create('Database_Sessions');

$log_container = new bucket_Container($factory);
$log_container->create('Database_Log');

Что-то в этом роде.Не похоже на ракетостроение.

Edit2: У меня недостаточно точек повторения, чтобы комментировать вопрос (немного глупо), но в ответ на ваше беспокойство по поводу «модульности»: воспринимайте контейнер как«клей» вашего приложения.Действительно, если у вас большое приложение, вы можете «склеить» только изолированную часть вашего приложения.Это действительная проблема инкапсуляции.Но даже тогда вам все еще нужен контейнер, который обрабатывает инъекцию на самом высоком уровне абстракции.Если вы просто создаете отдельный контейнер для каждой части своего приложения, вы либо в конечном итоге сталкиваетесь с ненужным дублированием экземпляров, либо вам приходится применять другой уровень управления экземплярами, который никак не улучшает инкапсуляцию:совместное использование экземпляров между различными частями вашего приложения.

Мой совет - использовать один контейнер на уровне начальной загрузки.Если вы хотите добавить инкапсуляцию для определенных частей вашего приложения (модулей, плагинов и т. Д.), Используйте «дочерние контейнеры».Дочерний контейнер наследует экземпляры от родительского контейнера, но родительский контейнер ничего не знает о дочернем (насколько он обеспокоен, он все еще холостяк;)).Может быть, Bucket поддерживает это по умолчанию, я знаю, что другие контейнеры DI делают.Если нет, то это действительно легко реализовать с помощью Decorator.Представьте себе что-то вроде этого:

class MyContainerType extends bucket_Container {

    private $_parent;
    private $_subject;

    public function  __construct($factory = null, bucket_Container $parent = null){
        $this->_parent = $parent;
        $this->_subject = new bucket_Container($factory);
    }

    public function get($key){
        $value = $this->_subject->get($key);
        if($value){
            return $value;
        }
        return $this->_parent->get($key);
    }
    /**
     * Override and delegation of all other methods
     */
}
6 голосов
/ 20 февраля 2011

Создание одного большого контейнера, включающего все возможные классы, которые могут понадобиться, кажется мне ничем иным, как контрпродуктивным, и я не могу представить, что это хорошая практика. Похоже, что рецепт плохой работы, по крайней мере.

Наоборот. Это именно то, что вы будете делать с контейнером di. Контейнер будет только создавать экземпляры объектов по требованию, поэтому практически нет накладных расходов на управление всеми классами singleton-ish через него.

Самая большая проблема с di - это различие между общими объектами (вещи, которые вы обычно рассматриваете как одиночные) и временными объектами (объекты, которые имеют множество экземпляров в обычном потоке приложения.). Первые легко управляются через di. Последнее не совсем подходит. Четко различать эти два «вида» объектов может показаться немного хлопотным, но на самом деле это очень полезный побочный эффект от использования контейнера di.

0 голосов
/ 19 февраля 2011

Если вас беспокоит несколько одновременных подключений, вы можете просто использовать mysql_pconnect () или equivelant для базы данных, которую вы используете. Он проверит, открыто ли уже соединение, и использует существующее соединение, если оно есть.

Что касается проблемы с контейнером, я видел, что это было решено двумя способами, о которых вы, кажется, знаете оба. Первый метод заключается в том, чтобы инфраструктура считывала схему вашей базы данных и создавала классы для каждой таблицы. Мне лично не нравится такой подход. Symfony - одна из платформ, которая делает это (используя доктрину ORM).

Более предпочтительный метод, который я видел, это иметь универсальный контейнер, который в основном строит sql для вас, учитывая таблицу, столбцы и действие. Это подход, принятый codeIgniter:

$query = $this->db->get('mytable');
$query = $this->db->get_where('mytable', array('id' => $id), $limit, $offset);
...