##// END OF EJS Templates
build.gradle cleanup and refactoring
build.gradle cleanup and refactoring

File last commit:

r31:b8fd9db91f14 propose observables
r97:8c240739e273 ts-plugin
Show More
di.md
204 lines | 11.3 KiB | text/x-minidsrc | MarkdownLexer
cin
added IoC container documentation
r31 # 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");
//...
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<DataContext>('db');
let db2 = childContainer.getService<DataContext>('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<DataContext>('db');
let db2 = childContainer.getService<DataContext>('db');
//db !== db2
childContainer.dispose(); // will dispose db2, db will be left intact
```