Как стать автором
Обновить

Комментарии 31

Хорошая статья, согласен с вами в рекомендации книг, Зандстра опирался на «Банду четырех», постоянно подчеркивая это в своей книге.
Вы уже упоминали о компонентах Symfony, хотелось бы добавить, что Symfony это набор всех паттернов, которые только есть и можно круто прокачать свой скилл, просто изучив исходники с помощью своей любимой IDE.
Да и книга хорошая. Помню когда увидел ее на книжном рынке, сразу же купил. Она очень доступна для понимания.
НЛО прилетело и опубликовало эту надпись здесь
Dependency Injection — явная передача объектов в методы класса, только так. Либо в конструктор (идеальный вариант), либо в init-методы (initServiceA()).

Здесь не ошибка, а скорее путаница определений, свойственная многим статьям/мануалам. Под заголовком Dependency Injection в статье описан Dependency Injection Container (DIC). Очень часто, когда пишут о DIС, «Container» опускают. На самом деле DI можно использовать без контейнера, как описано в примере в начале:

public function __construct(Grabber $grabber, HtmlExtractor $filter)

Очень хорошо это поясняет Anthony Ferrara в обучающем видео:
www.youtube.com/watch?v=IKD2-MAkXyQ
Спасибо, за отзывы. В статье я попытался объяснить больше «практическую проблему», т.е. почему не стоит дубилровать код создания объектов, почему стоит выносить инциализацию объектов за пределы класса, и каким образом можно организовать работу по созданию объектов внутри приложения. Насколько я понимаю, BoneFletcher прав, слово Container обычно опускают, когда говорят о DI. И, если не ошибаюсь, тот же Фаулер об этом писал. Вообще, в терминологии тяжело запутаться :) Так что я не стану спорить…
Однозначно, в закладки. Спасибо за хорошую публикацию
Спасибо, за отзыв. Приятно знать, что не зря старался :)
Можно параметры объекта описывать в конфиге:
$configGF = [
    'filter' => 'MyMock',
];

$finder = new GoogleFinder($config);


А уже GoogleFinder в своём конструкторе смотрит в конфиг и для $this->finder создаёт экземпляр класса MyMock. А для тех, что не определены, использует классы по умолчанию ($this->grabber = new Grabber()).
Да, конечно. Я просто хотел показать самую простую реализацию :)
Пару раз использовал подобный прием, но почему-то конструкция типа $this->filter = new $configGF['filter]; (равно как $function_name()) мне жутко не нравится. Вы часто используете?
Не в конструкции дело. Конструкции можно любые сделать, да и PHP итак не самый красивый язык в плане конструкций.

Я о самом принципе.
Есть класс, экземпляры которого могут иметь настраиваемое поведение (реализации вложенных подсистем лишь один из видов настройки).
Отдельно пишем класс по возможности обобщённо, отдельно конфиги к нему. А объект сам по своему конфигу себя инициализирует, как хочет.

Не надо вызывать потом отдельно методы, подсовывать ему незаметно какие-то объекты в поля. Сразу после конструктора у нас завершённый, неизменяемый, готовый к работе объект.
Есть вопрос. Даже два.
1. В статье, как пример реализации паттерна Service locator, написан ServiceContainer, он хранит все пользовательские объекты. Так почему же он называется ServiceContainer? В нем не храняться классы-сервисы, которые как раз и вводят слой сервисов. Он просто используется для разруливания зависимостей под капотом.
2. DIC — это реализация паттерна Service locator?
1) Тут слой сервисов немного не причем :) Идея Service Locator в том чтобы отвязать логику инициализации объекта от логики его использования. Ты просто как бы говоришь контейнеру, дай мне этот объект. И он создает его со всеми зависимостями, которые тебе нужны или берет уже существующий объект, если он был инициализирован раньше. Не знаю, почему назвали именно Service Locator, как-то видимо так исторически сложилось :)

2) Не совсем так. Вы можете использовать и там и там одну реализацию контейнера. Когда вы создаете классы, которые будут сами обращаться к контейнеру и получать у него нужные сервисы – это Service Locator. Когда фреймворк берет на себя все обязанности по созданию объектов и у Вас нет клиентских классв, которые имеют зависимость на контейнер – это Dependency Injection. Т.е. в случае с Web приложением. Фреймворк принимает запрос, сопоставляет его сервису (вы должны будете в конфигруации роутов указать ключ по которому сервис доступен а контейнере), получает сервис из контейнера и передает в него запрос. Как-то так.
Вот и я о том же, сервисы (слой сервисов) тут ни при чем. Т.е. ServiceContainer хранит на самом деле не сервисы, а произвольные пользовательские объекты? Если да, то в чем тогда отличие от DIC? DIC тоже хранит пользовательские объекты, и автоматом по перому требованию инициализирует зависимости.

Начал читать второй ваш абзац, и понимать разницу между DI и Service locator. Dependecy Injection — это паттерн, при котором необходимые зависимости содержаться как свойства зависимого класса, а Service locator — при котором зависимый класс является ServiceLocatorAware и получает зависимости у контейнера напрамую.

Все верно?
Да, верно. DI контейнер предполагает ни один клиентский класс, ничего не знает о том, что какой-то контейнер вообще существует. В одном месте фреймворк получает один! необходимый сервис и передает управление ему.
Спасибо за интересную статью — как раз сейчас интересуюсь этой темой :)
Спасибо за статью, освежил и структурировал знания :)
Будут ли еще статьи в таком же ключе?
Я планировал еще статью конкретно по Symfony Dependency Injecton Component. Хочется поделиться некоторыми идеями и получить на них фидбек. Но я достаточно кропотливо отношусь к написанию статей, поэтому не обещаю, что это будет скоро :)
Еще такой вопрос: а если у нас в классе есть метод, в котором нужно использовать не один какой-то конкретный объект, который мы можем передать в аргументе, а нужно создать и использовать несколько объектов определенного класса? Как в таком случае поможет DI?
Как правило, Вам нужно получать один и тот же экземпляр класса из контейнера. Для этого можно сохранять (кэшировать) готовые объекты в свойство контейнера (ключ сервиса => готовый объект). Потом простая проверка, если объект тут, возвращаем, если не тут, создаем и ложим в кэш.

Иногда бывает нужно каждый раз получать новый экземпляр класса. Для этого Вам всего лишь нужно перестать кэшировать объект (либо просто клонировать первый созданный). В моем коде вы будете получать каждый раз новый объект.
Я немного другое имел в виду. Возьмем такой примитивный пример:

class BooksService {
	
	public function booksMethod()
	{
		$book1 = new Book;
		$book1->someAttr = 'val1';
		$book1->someMethod();

		// ...

		$book5 = new Book;
		$book5->someAttr = 'val5';
		$book5->anotherAttr = 'anotherVal';
		$book5->someMethod();

		// ...
	}
}


Как мне с помощью Dependency injection добиться того, чтобы класс BooksService не зависел жестко от класса Book, и также не был бы Container Aware классом? Такое возможно?
Да, в принципе. Все зависит от того насколько реален пример и почему Вам так надо сделать :) В Вашем случае я вижу 2 решения.

1) Через контейнер заинжектить 1 Book и клонировать его по необходимости.
2) Некоторые контейнеры поддерживают постинициализацию. Вы можете создать метод addBook(Book $book) и попросить контейнер, чтобы он вызвал этот метод после создания объекта.

Вообще, контейнер не предназначен для работы с сущностями. Инжектить что-нибудь в сущности считается плохой практикой, как и инжектить сущности в сервисы. В Вашем случае я бы не рекомендовал использовать контейнер.
Насчет клонирования объекта — смысл понятен)
А что бы вы порекомендовали в случае сущностей и сервисов? Если не использовать DI контейнер, то оставлять их жестко зависимыми от других классов?
Где-нибудь есть хорошее описание таких вещей — для каких случаев контейнер предназначен/не предназначен, насколько правильно инжектить в сущности и т.д.?
Вы знаете, этот вопрос достаточно сложный и тут нет однозначных ответов. Все зависит от того каких взглядов на Модель вы придерживаетесь. Если вы сторонник анемичных моделей (в модели только данные, простые хелперы и отношения между сущностями, бизнесс логика в слое сервисов), то не станете мешать в кучу бизнесс логику и сущности, соответственно с вашей точки зрения будет неправильно ложить инициализацию сущностей в контейнер. Если вы сторонник Rich Model, то там будут работать совсем другие принципы и правила.

Я не стану Вам в принципе давать никаких рекомендаций на этот счет. Но если хотите знать мое мнение, то я считаю, что жесткая связь оправдана в сущностях. Я бы даже запретил использовать интерфейсы для сущностей :) Так или иначе вы работатете не с абстракциями а объектами вполне конкретного типа. Если используется наследования, то можно использовать зависимости на базовый класс. С моей! точки зрения это правильно. Я думаю вы поняли, что я сторонник анемичной модели :)
Если честно, сейчас больше интересует подход с сервисными слоями. Я уже применял принцип «всю логику помещать в модель» — модели очень сильно разрастались)
Понятно =) Как бы то ни было, спасибо за информацию)
Все зависит от того каких взглядов на Модель вы придерживаетесь.

Редкая политкорректность :)
Это можно сделать через фабрику:

class BooksService
{
    private $bookFactory;

    public function __construct($bookFactory)
    {
        $this->bookFactory = $bookFactory;
    }
    
    public function booksMethod()
    {
        $book1 = $this->bookFactory->create();
        $book5 = $this->bookFactory->create();
    }
}

Но только если это реально необходимо. Если можно обойтись без абстракций, то лучше делать без них.
Скорее так:

return new Controller($container->get('finder'));
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации