Подтвердить что ты не робот

Имея проблемы с пониманием инъекции зависимостей

Я создаю небольшой проект, чтобы попытаться научить себя как можно большей части основ, что для меня означает не использовать сборную фреймворк (как Jeff как-то положил его, "Не изобретайте колесо, , если вы не планируете больше узнать о колесах" [основное внимание]) и следуя принципам разработки, управляемой испытаниями.

В моих поисках я недавно столкнулся с концепцией Dependency Injection, которая кажется существенной для TDD. Моя проблема в том, что я не могу обернуть вокруг себя. До сих пор я понимаю, что это более или менее означает, что "вызывающий абонент передает классу/методу любые другие классы, которые могут ему понадобиться, а не позволять им сами создавать их".

У меня есть два примера проблем, которые я пытаюсь разрешить с помощью DI. Я на правильном пути с этими рефакторингами?

Подключение к базе данных

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

class Post {  
  private $id;  
  private $body;  

  public static function getPostById($id) {  
    $db = Database::getDB();  
    $db->query("SELECT...");  
    //etc.  
    return new Post($id, $body);
  }  

  public function edit($newBody) {  
    $db = Database::getDB();  
    $db->query("UPDATE...");  
    //etc.  
  }  
}  

С DI, я думаю, он будет выглядеть примерно так:

class Post {  
  private $db; // new member

  private $id;  
  private $body;  

  public static function getPostById($id, $db) { // new parameter   
    $db->query("SELECT...");  // uses parameter
    //etc.  
    return new Post($db, $id, $body);
  }  

  public function edit($id, $newBody) {   
    $this->db->query("UPDATE...");  // uses member
    //etc.  
  }  
} 

Я все еще могу использовать синглтон с учетными данными, указанными в настройке приложения, но мне просто нужно передать его с контроллера (все равно контроллеры, которые не проверяются на единицу):

Post::getPostById(123, Database::getDB);

Модели, вызывающие модели

Возьмем, например, сообщение, которое имеет счетчик просмотров. Поскольку логика определения нового вида не является специфичной для объекта Post, он просто должен быть статическим методом на своем собственном объекте. Объект Post затем называет его:

class Post {
  //...

  public function addView() {
    if (PageView::registerView("post", $this->id) {
     $db = Database::getDB();
     $db->query("UPDATE..");
     $this->viewCount++;
   }
}

С DI, я думаю, это выглядит более похоже:

class Post {
  private $db;
  //...

  public function addView($viewRegistry) {
    if ($viewRegistry->registerView("post", $this->id, $this->db) {
     $this->db->query("UPDATE..");
     $this->viewCount++;
   }
}

Это изменяет вызов от контроллера к этому:

$post->addView(new PageView());

Это означает создание экземпляра нового экземпляра класса, который имеет только статические методы, который плохо пахнет мне (и я думаю, что это невозможно на некоторых языках, но возможно здесь, потому что PHP не позволяет самим классам статично).

В этом случае мы переходим только на один уровень, поэтому наличие экземпляра контроллера все кажется работоспособным (хотя класс PageView получает косвенное соединение с БД через переменную-член Post), но похоже, что он может получить громоздкий, если вам нужно было вызвать метод, которому нужен класс, которому нужен класс, которому нужен класс. Полагаю, это может означать, что код тоже пахнет.

Я на правильном пути с этим, или я полностью неправильно понял DI? Любые критические замечания и предложения приветствуются.

4b9b3361

Ответ 1

Да. Похоже, у вас есть правильная идея. Вы увидите, что при реализации DI все ваши зависимости будут плавать до "вершины". Наличие всего на вершине облегчит издеваться над необходимыми объектами для тестирования.

Наличие класса, которому нужен класс, который нуждается в классе, не так уж плохо. То, что вы описываете, есть ваш граф объектов. Это нормально для DI. Давайте возьмем объект "Дом" в качестве примера. Он имеет зависимость от кухни; Кухня имеет зависимость от раковины; Раковина имеет зависимость от смесителя и так далее. Создание экземпляра дома выглядело бы как new House(new Kitchen(new Sink(new Faucet()))). Это помогает обеспечить соблюдение принципа единой ответственности. (В стороне вы должны сделать эту работу по созданию экземпляра в чем-то вроде factory или строителя для дальнейшего соблюдения принципа единой ответственности.)

Misko Hevery много писал о DI. Его блог - отличный ресурс. Он также указал на некоторые из общих недостатков (конструктор действительно работает, копается в коллаборационисты, хрупкое глобальное состояние и синглтоны, а класс делает слишком много) с предупреждающими знаками, чтобы заметить их и способы их исправления. Стоит проверить когда-нибудь.

Ответ 2

Это, безусловно, идет в правильном направлении, но вы не должны останавливаться на достигнутом.

Точка DI - это удаление сильных связей между классами, чтобы упростить замену отдельных компонентов. Это позволит улучшить тестируемость, потому что вы можете легче заменять зависимости с помощью Mocks и Stubs. И как только ваш код будет протестирован, его легко изменить и поддерживать.

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

Для получения дополнительной информации об этом см.


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

Ответ 3

Инъекционная инъекция - это инъекция. Вам нужно какое-то решение для ввода внешнего объекта.

Традиционными подходами являются:

  • впрыск конструктора __construnctor($dependecy) {$this->_object = $dependency}
  • инъекция установщика setObject($dependency) {$this->_object = $dependency}
  • gettter injection getObject() {return $this->_dependency} и oveloading этот метод, например. от заглушки или макета в тестах.

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

Избегайте статических вызовов. В моем личном правиле используется статическое значение только при вызове некоторых функций, например. My::strpos() или при работе с синглонами или реестром (который должен быть ограничен минимумом, поскольку глобальное состояние является злым).

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

Взгляните на другие зависимые инъекции + [php] темы на SO.

Изменить после комментария:

Контейнер

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

Контейнер может создавать все ресурсы, которые вам нужны во время загрузки, или ленить загрузку ресурса при доступе (лучшее решение).

В качестве примера рассмотрим:

Еще одна отличная ссылка:

Ответ 4

Чтобы ответить на ваши вопросы: да, вы на правильном пути. Чтобы дать вам более подробную информацию: это один из лучших сообщений, которые я нашел связанным с DI:

http://www.potstuck.com/2009/01/08/php-dependency-injection

Вы поймете, что такое контейнер: $ book = Контейнер:: makeBook();

Что касается второго примера: в вашем методе addView я попытался бы избежать передачи объекта $viewRegistry, я бы проверил условие снаружи в контроллере.