# HG changeset patch # User cin # Date 2018-10-14 18:39:21 # Node ID b8fd9db91f14bc2481b044a7575dc76591c61f3a # Parent 824c675d7329ae70be1df08fc07afc331c7a121a added IoC container documentation diff --git a/.project b/.project deleted file mode 100644 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - core - Project core created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/docs/ru/di.md b/docs/ru/di.md new file mode 100644 --- /dev/null +++ b/docs/ru/di.md @@ -0,0 +1,205 @@ +# SOLID и контейнеры + +Рассмотрим простой пример того, как может быть построено приложения и почему приходится прибегать к такому сложному и непонятному решению как контейнеры. + +Допустим у нас есть приложение, которое работает с комментариями к товару, и мы решили разделить логику и работу с источником данных, разнеся ее между контроллером и контекстом данных и тут возникает вопрос, как они должны взаимодействовать друг с другом. + +```ts +// BAD +class CommentsController { + + _db : DataContext, + + constructor(connectionString: string) { + // explicit dependency + this._db = new PgSqlDataContext(connectionString); + } + + //... +} + +// GOOD +class CommmentsController { + + _db : DataContext, + + constructor(dataContext: DataContext) { + // a dependency is passed through the constructor + this._db = dataContext; + } + + //... +} + +// the container will do all the work +// the container knows which DataContext to create +let commentsController = container.getService("commmentsController"); + +//... + +commmentsController.createComment({ text: "Hello, unfair world!" }); + +``` + +Для ответа на вопрос, что такое хорошо и что такое - плохо существует *SOLID* - набор принципов ООП при разработки программного обеспечения + +- **S**ingle responsibility +- **O**pen-closed +- **L**iskov substitution +- **I**nterface segregation +- **D**ependency inversion + +Использование данных принципов позволяет писать код, которыей будет обладать + +- повоторной используемостью +- тестируемостью +- читаемостью +- поддаваться доработкам + +*IoC* Контейнеры являются инструментом для построения ПО согласно принциам SOLID, как клей связывают друг с другом компоненты. + +Контейнеры предоставляют стандарный механизм для описания и конструирования объектов, т.е. это некоторый объект в котором сосредоточена информация о сруктуре приложения и именно он должен создавать объекты и устанавливать между ними связи. Контейнер похож на шаблоны *абстрактная фабрика (Abstract Factory)* и *строитель (Builder)*. + +Для построения графа объектов контейнеру требуется конфигурация, которая состоит из набора дескрипторов сервисов, а также зависимостями между ними. + +Сервисы регистрируются в контейнере, каждый сервис имеет свое имя, позволяющее получить его у контейнера. + +```js + +await container.configure({ + db: { + $type : 'my/app/PgSqlDataContext', + params: { + host: 'localhost', + port: 5432 + } + }, + commmentsController: { + $type: 'my/app/CommmentsController', + params: {$dependency: 'db'} + } +}); + +``` + +В приведенном примере в контейнере объявляются два сервиса, один для работы с базой, воторой - для работы с комментариями. Между сервисами устанавливается зависимость, при создании `commmentsController` ему в контроллер передается созданный экземпляр контекста данных. + +Приложению остается только получить нужный сервис у контейнера и воспользоваться последним. + +> **Правило:** объекты не должны знать про контейнер! + +Очень важно соблюдать данное правило, поскольку если внутри класса будет использоваться контейнер, то он может запрашивать любые сервисы в любое время, что сильно усложнит отслеживание зависимостей. Исключением могут быть только объекты отвечающие за жизненный цикл приложения, которые создают и конфигурируют контейнер и не участвуют в бизнес-логике. + +Конфигурация контейнера является асинхронной операцией, поскольку может привести к загрузке модулей, где находятся объявления типов. Полностью сконфигурированный контейнер позволяет получать сервисы уже синхронно, что упрощает работу с ним и не тратит дополнительные ресурсы на использование асинхронных операций. + +## Конфигурация + +Контейнер представляет собой словарь дескрипторов содержащих информацию о сервисах, в роли сервисов можно зарегистрировать: + +- типы +- фабричные методы +- существующие объекты и простые занчения + +подробоное описание конфигурации контейнера [di-config.md](di-config.md) + +## Вложенные контейнеры + +Контейнеры могут создаваться на основе уже существующих, так называемые дочерние контейнеры, они получают все сервисы описанные в родительском контейнере. + +При изменении конфигурации дочернего контейнера - родительский контейнер останется без изменений. Использование дочерних контейнеров позволяет оптимизировать конфигурирование и дальнейшую работу с сервисами. + +Примером такой оптимизации может служить веб-приложение, в котором загружается контейнер для всего приложения, а для каждого запроса создается свой дочерний контейнер, который донастраивается контроллером запроса. По окончанию выполнения запроса дочерний контейнр уничтожается, освобождая ресурсы. + +## Активация сервисов + +Активация - процесс, когда контейнер в ответ на запрос выдает экземпляр сервиса. При обработке запроса на получение сервиса контейнер создает контекст активации, всю работу по получению экземпляра сервиса выполняет дескриптор, которому передается контекст активации. + +1. создается контекст активации, +2. ищется запись декриптора сервиса в контейнере, +3. дескриптору передается контекст активации, +4. дескриптор возвращает экземпляр сервиса. + +В процессе создания экземпляра сервиса, дескриптор может использовать контекст активации для обращения к контейнеру, текущим сервисам, а также может использовать его для активации других дескрипторов. + +### Типы активации сервисов + +Тип активации относится к сервисам, в качестве которых были зарегистрированы либо типы, либо фабричные методы. + +```ts + +container.configure({ + foo: { + $type: 'my/ServiceClass', + activation: 'container' + }, + bar: { + $factory: () => { + return new ServiceClass(); + }, + activation: 'context' + } +}); + +``` + +#### call + +Тип активации по-умолчанию, создается каждый раз, когда сервис запрашивается. + +#### context + +Экземпляр будет создан только один раз в рамках текущего контекста активации, т.е. при разрешении зависимостей будет использоваться все время один и тотже экземплар. + +#### container + +Будет создан только один экземпляр для контейнера, где сервис зарегистрирован. Созданный экземпляр сервиса будет автоматически очищен при освобождении контейнера. + +```ts +container.cofigure({ + db: { + $type : 'my/app/PgSqlDataContext', + activation: 'container', + params: { + host: 'localhost', + port: 5432 + } + } +}) + +let db = container.getService('db'); + +let db2 = childContainer.getService('db'); + +//db === db2 + +container.dispose(); // will dispose db + +``` + +#### hierarchy + +Будет создан только один экземпляр для контейнера, который создал контекст активации. Созданный экземпляр червиса будет автоматически очищен при освобождении контейнера. + +Данный вариант похож на тип активации `container`, но позволяет описать сервисы в родительском контейнере, а управлять временем жизни экземпляров этих сервисов при помощи дочерних контейнеров. Такой подход позволяет оптимизировать конфигурацию контейнеров. + +```ts +container.cofigure({ + db: { + $type : 'my/app/PgSqlDataContext', + activation: 'hierarchy', + params: { + host: 'localhost', + port: 5432 + } + } +}) + +let db = container.getService('db'); + +let db2 = childContainer.getService('db'); + +//db !== db2 + +childContainer.dispose(); // will dispose db2, db will be left intact + +``` \ No newline at end of file