Комментарии 31
Хорошая статья, согласен с вами в рекомендации книг, Зандстра опирался на «Банду четырех», постоянно подчеркивая это в своей книге.
Вы уже упоминали о компонентах Symfony, хотелось бы добавить, что Symfony это набор всех паттернов, которые только есть и можно круто прокачать свой скилл, просто изучив исходники с помощью своей любимой IDE.
Вы уже упоминали о компонентах 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. И, если не ошибаюсь, тот же Фаулер об этом писал. Вообще, в терминологии тяжело запутаться :) Так что я не стану спорить…
Однозначно, в закладки. Спасибо за хорошую публикацию
Можно параметры объекта описывать в конфиге:
А уже GoogleFinder в своём конструкторе смотрит в конфиг и для $this->finder создаёт экземпляр класса MyMock. А для тех, что не определены, использует классы по умолчанию ($this->grabber = new Grabber()).
$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, написан ServiceContainer, он хранит все пользовательские объекты. Так почему же он называется ServiceContainer? В нем не храняться классы-сервисы, которые как раз и вводят слой сервисов. Он просто используется для разруливания зависимостей под капотом.
2. DIC — это реализация паттерна Service locator?
1) Тут слой сервисов немного не причем :) Идея Service Locator в том чтобы отвязать логику инициализации объекта от логики его использования. Ты просто как бы говоришь контейнеру, дай мне этот объект. И он создает его со всеми зависимостями, которые тебе нужны или берет уже существующий объект, если он был инициализирован раньше. Не знаю, почему назвали именно Service Locator, как-то видимо так исторически сложилось :)
2) Не совсем так. Вы можете использовать и там и там одну реализацию контейнера. Когда вы создаете классы, которые будут сами обращаться к контейнеру и получать у него нужные сервисы – это Service Locator. Когда фреймворк берет на себя все обязанности по созданию объектов и у Вас нет клиентских классв, которые имеют зависимость на контейнер – это Dependency Injection. Т.е. в случае с Web приложением. Фреймворк принимает запрос, сопоставляет его сервису (вы должны будете в конфигруации роутов указать ключ по которому сервис доступен а контейнере), получает сервис из контейнера и передает в него запрос. Как-то так.
2) Не совсем так. Вы можете использовать и там и там одну реализацию контейнера. Когда вы создаете классы, которые будут сами обращаться к контейнеру и получать у него нужные сервисы – это Service Locator. Когда фреймворк берет на себя все обязанности по созданию объектов и у Вас нет клиентских классв, которые имеют зависимость на контейнер – это Dependency Injection. Т.е. в случае с Web приложением. Фреймворк принимает запрос, сопоставляет его сервису (вы должны будете в конфигруации роутов указать ключ по которому сервис доступен а контейнере), получает сервис из контейнера и передает в него запрос. Как-то так.
Вот и я о том же, сервисы (слой сервисов) тут ни при чем. Т.е. ServiceContainer хранит на самом деле не сервисы, а произвольные пользовательские объекты? Если да, то в чем тогда отличие от DIC? DIC тоже хранит пользовательские объекты, и автоматом по перому требованию инициализирует зависимости.
Начал читать второй ваш абзац, и понимать разницу между DI и Service locator. Dependecy Injection — это паттерн, при котором необходимые зависимости содержаться как свойства зависимого класса, а Service locator — при котором зависимый класс является ServiceLocatorAware и получает зависимости у контейнера напрамую.
Все верно?
Начал читать второй ваш абзац, и понимать разницу между DI и Service locator. Dependecy Injection — это паттерн, при котором необходимые зависимости содержаться как свойства зависимого класса, а Service locator — при котором зависимый класс является ServiceLocatorAware и получает зависимости у контейнера напрамую.
Все верно?
Спасибо за интересную статью — как раз сейчас интересуюсь этой темой :)
Спасибо за статью, освежил и структурировал знания :)
Будут ли еще статьи в таком же ключе?
Будут ли еще статьи в таком же ключе?
Еще такой вопрос: а если у нас в классе есть метод, в котором нужно использовать не один какой-то конкретный объект, который мы можем передать в аргументе, а нужно создать и использовать несколько объектов определенного класса? Как в таком случае поможет DI?
Как правило, Вам нужно получать один и тот же экземпляр класса из контейнера. Для этого можно сохранять (кэшировать) готовые объекты в свойство контейнера (ключ сервиса => готовый объект). Потом простая проверка, если объект тут, возвращаем, если не тут, создаем и ложим в кэш.
Иногда бывает нужно каждый раз получать новый экземпляр класса. Для этого Вам всего лишь нужно перестать кэшировать объект (либо просто клонировать первый созданный). В моем коде вы будете получать каждый раз новый объект.
Иногда бывает нужно каждый раз получать новый экземпляр класса. Для этого Вам всего лишь нужно перестать кэшировать объект (либо просто клонировать первый созданный). В моем коде вы будете получать каждый раз новый объект.
Я немного другое имел в виду. Возьмем такой примитивный пример:
Как мне с помощью Dependency injection добиться того, чтобы класс BooksService не зависел жестко от класса Book, и также не был бы Container Aware классом? Такое возможно?
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) и попросить контейнер, чтобы он вызвал этот метод после создания объекта.
Вообще, контейнер не предназначен для работы с сущностями. Инжектить что-нибудь в сущности считается плохой практикой, как и инжектить сущности в сервисы. В Вашем случае я бы не рекомендовал использовать контейнер.
1) Через контейнер заинжектить 1 Book и клонировать его по необходимости.
2) Некоторые контейнеры поддерживают постинициализацию. Вы можете создать метод addBook(Book $book) и попросить контейнер, чтобы он вызвал этот метод после создания объекта.
Вообще, контейнер не предназначен для работы с сущностями. Инжектить что-нибудь в сущности считается плохой практикой, как и инжектить сущности в сервисы. В Вашем случае я бы не рекомендовал использовать контейнер.
Насчет клонирования объекта — смысл понятен)
А что бы вы порекомендовали в случае сущностей и сервисов? Если не использовать DI контейнер, то оставлять их жестко зависимыми от других классов?
Где-нибудь есть хорошее описание таких вещей — для каких случаев контейнер предназначен/не предназначен, насколько правильно инжектить в сущности и т.д.?
А что бы вы порекомендовали в случае сущностей и сервисов? Если не использовать DI контейнер, то оставлять их жестко зависимыми от других классов?
Где-нибудь есть хорошее описание таких вещей — для каких случаев контейнер предназначен/не предназначен, насколько правильно инжектить в сущности и т.д.?
Вы знаете, этот вопрос достаточно сложный и тут нет однозначных ответов. Все зависит от того каких взглядов на Модель вы придерживаетесь. Если вы сторонник анемичных моделей (в модели только данные, простые хелперы и отношения между сущностями, бизнесс логика в слое сервисов), то не станете мешать в кучу бизнесс логику и сущности, соответственно с вашей точки зрения будет неправильно ложить инициализацию сущностей в контейнер. Если вы сторонник Rich Model, то там будут работать совсем другие принципы и правила.
Я не стану Вам в принципе давать никаких рекомендаций на этот счет. Но если хотите знать мое мнение, то я считаю, что жесткая связь оправдана в сущностях. Я бы даже запретил использовать интерфейсы для сущностей :) Так или иначе вы работатете не с абстракциями а объектами вполне конкретного типа. Если используется наследования, то можно использовать зависимости на базовый класс. С моей! точки зрения это правильно. Я думаю вы поняли, что я сторонник анемичной модели :)
Я не стану Вам в принципе давать никаких рекомендаций на этот счет. Но если хотите знать мое мнение, то я считаю, что жесткая связь оправдана в сущностях. Я бы даже запретил использовать интерфейсы для сущностей :) Так или иначе вы работатете не с абстракциями а объектами вполне конкретного типа. Если используется наследования, то можно использовать зависимости на базовый класс. С моей! точки зрения это правильно. Я думаю вы поняли, что я сторонник анемичной модели :)
Если интересуетесь Rich model и Symfony, советую посмотреть этот доклад 2012.symfonycamp.org.ua/speaker-lineup/kirill-chebunin/ :)
Понятно =) Как бы то ни было, спасибо за информацию)
Все зависит от того каких взглядов на Модель вы придерживаетесь.
Редкая политкорректность :)
Это можно сделать через фабрику:
Но только если это реально необходимо. Если можно обойтись без абстракций, то лучше делать без них.
class BooksService
{
private $bookFactory;
public function __construct($bookFactory)
{
$this->bookFactory = $bookFactory;
}
public function booksMethod()
{
$book1 = $this->bookFactory->create();
$book5 = $this->bookFactory->create();
}
}
Но только если это реально необходимо. Если можно обойтись без абстракций, то лучше делать без них.
Мне кажется, что при написании раздела о DI, Вы имели ввиду:
return new Controller(GoogleFinder $finder);
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Проблема инициализации объектов в ООП приложениях на PHP. Поиск решения при помощи шаблонов Registry, Factory Method, Service Locator и Dependency Injection