|
|
@@
-0,0
+1,205
|
|
|
|
1
|
# SOLID и контейнеры
|
|
|
|
2
|
|
|
|
|
3
|
Рассмотрим простой пример того, как может быть построено приложения и почему приходится прибегать к такому сложному и непонятному решению как контейнеры.
|
|
|
|
4
|
|
|
|
|
5
|
Допустим у нас есть приложение, которое работает с комментариями к товару, и мы решили разделить логику и работу с источником данных, разнеся ее между контроллером и контекстом данных и тут возникает вопрос, как они должны взаимодействовать друг с другом.
|
|
|
|
6
|
|
|
|
|
7
|
```ts
|
|
|
|
8
|
// BAD
|
|
|
|
9
|
class CommentsController {
|
|
|
|
10
|
|
|
|
|
11
|
_db : DataContext,
|
|
|
|
12
|
|
|
|
|
13
|
constructor(connectionString: string) {
|
|
|
|
14
|
// explicit dependency
|
|
|
|
15
|
this._db = new PgSqlDataContext(connectionString);
|
|
|
|
16
|
}
|
|
|
|
17
|
|
|
|
|
18
|
//...
|
|
|
|
19
|
}
|
|
|
|
20
|
|
|
|
|
21
|
// GOOD
|
|
|
|
22
|
class CommmentsController {
|
|
|
|
23
|
|
|
|
|
24
|
_db : DataContext,
|
|
|
|
25
|
|
|
|
|
26
|
constructor(dataContext: DataContext) {
|
|
|
|
27
|
// a dependency is passed through the constructor
|
|
|
|
28
|
this._db = dataContext;
|
|
|
|
29
|
}
|
|
|
|
30
|
|
|
|
|
31
|
//...
|
|
|
|
32
|
}
|
|
|
|
33
|
|
|
|
|
34
|
// the container will do all the work
|
|
|
|
35
|
// the container knows which DataContext to create
|
|
|
|
36
|
let commentsController = container.getService<CommmentsController>("commmentsController");
|
|
|
|
37
|
|
|
|
|
38
|
//...
|
|
|
|
39
|
|
|
|
|
40
|
commmentsController.createComment({ text: "Hello, unfair world!" });
|
|
|
|
41
|
|
|
|
|
42
|
```
|
|
|
|
43
|
|
|
|
|
44
|
Для ответа на вопрос, что такое хорошо и что такое - плохо существует *SOLID* - набор принципов ООП при разработки программного обеспечения
|
|
|
|
45
|
|
|
|
|
46
|
- **S**ingle responsibility
|
|
|
|
47
|
- **O**pen-closed
|
|
|
|
48
|
- **L**iskov substitution
|
|
|
|
49
|
- **I**nterface segregation
|
|
|
|
50
|
- **D**ependency inversion
|
|
|
|
51
|
|
|
|
|
52
|
Использование данных принципов позволяет писать код, которыей будет обладать
|
|
|
|
53
|
|
|
|
|
54
|
- повоторной используемостью
|
|
|
|
55
|
- тестируемостью
|
|
|
|
56
|
- читаемостью
|
|
|
|
57
|
- поддаваться доработкам
|
|
|
|
58
|
|
|
|
|
59
|
*IoC* Контейнеры являются инструментом для построения ПО согласно принциам SOLID, как клей связывают друг с другом компоненты.
|
|
|
|
60
|
|
|
|
|
61
|
Контейнеры предоставляют стандарный механизм для описания и конструирования объектов, т.е. это некоторый объект в котором сосредоточена информация о сруктуре приложения и именно он должен создавать объекты и устанавливать между ними связи. Контейнер похож на шаблоны *абстрактная фабрика (Abstract Factory)* и *строитель (Builder)*.
|
|
|
|
62
|
|
|
|
|
63
|
Для построения графа объектов контейнеру требуется конфигурация, которая состоит из набора дескрипторов сервисов, а также зависимостями между ними.
|
|
|
|
64
|
|
|
|
|
65
|
Сервисы регистрируются в контейнере, каждый сервис имеет свое имя, позволяющее получить его у контейнера.
|
|
|
|
66
|
|
|
|
|
67
|
```js
|
|
|
|
68
|
|
|
|
|
69
|
await container.configure({
|
|
|
|
70
|
db: {
|
|
|
|
71
|
$type : 'my/app/PgSqlDataContext',
|
|
|
|
72
|
params: {
|
|
|
|
73
|
host: 'localhost',
|
|
|
|
74
|
port: 5432
|
|
|
|
75
|
}
|
|
|
|
76
|
},
|
|
|
|
77
|
commmentsController: {
|
|
|
|
78
|
$type: 'my/app/CommmentsController',
|
|
|
|
79
|
params: {$dependency: 'db'}
|
|
|
|
80
|
}
|
|
|
|
81
|
});
|
|
|
|
82
|
|
|
|
|
83
|
```
|
|
|
|
84
|
|
|
|
|
85
|
В приведенном примере в контейнере объявляются два сервиса, один для работы с базой, воторой - для работы с комментариями. Между сервисами устанавливается зависимость, при создании `commmentsController` ему в контроллер передается созданный экземпляр контекста данных.
|
|
|
|
86
|
|
|
|
|
87
|
Приложению остается только получить нужный сервис у контейнера и воспользоваться последним.
|
|
|
|
88
|
|
|
|
|
89
|
> **Правило:** объекты не должны знать про контейнер!
|
|
|
|
90
|
|
|
|
|
91
|
Очень важно соблюдать данное правило, поскольку если внутри класса будет использоваться контейнер, то он может запрашивать любые сервисы в любое время, что сильно усложнит отслеживание зависимостей. Исключением могут быть только объекты отвечающие за жизненный цикл приложения, которые создают и конфигурируют контейнер и не участвуют в бизнес-логике.
|
|
|
|
92
|
|
|
|
|
93
|
Конфигурация контейнера является асинхронной операцией, поскольку может привести к загрузке модулей, где находятся объявления типов. Полностью сконфигурированный контейнер позволяет получать сервисы уже синхронно, что упрощает работу с ним и не тратит дополнительные ресурсы на использование асинхронных операций.
|
|
|
|
94
|
|
|
|
|
95
|
## Конфигурация
|
|
|
|
96
|
|
|
|
|
97
|
Контейнер представляет собой словарь дескрипторов содержащих информацию о сервисах, в роли сервисов можно зарегистрировать:
|
|
|
|
98
|
|
|
|
|
99
|
- типы
|
|
|
|
100
|
- фабричные методы
|
|
|
|
101
|
- существующие объекты и простые занчения
|
|
|
|
102
|
|
|
|
|
103
|
подробоное описание конфигурации контейнера [di-config.md](di-config.md)
|
|
|
|
104
|
|
|
|
|
105
|
## Вложенные контейнеры
|
|
|
|
106
|
|
|
|
|
107
|
Контейнеры могут создаваться на основе уже существующих, так называемые дочерние контейнеры, они получают все сервисы описанные в родительском контейнере.
|
|
|
|
108
|
|
|
|
|
109
|
При изменении конфигурации дочернего контейнера - родительский контейнер останется без изменений. Использование дочерних контейнеров позволяет оптимизировать конфигурирование и дальнейшую работу с сервисами.
|
|
|
|
110
|
|
|
|
|
111
|
Примером такой оптимизации может служить веб-приложение, в котором загружается контейнер для всего приложения, а для каждого запроса создается свой дочерний контейнер, который донастраивается контроллером запроса. По окончанию выполнения запроса дочерний контейнр уничтожается, освобождая ресурсы.
|
|
|
|
112
|
|
|
|
|
113
|
## Активация сервисов
|
|
|
|
114
|
|
|
|
|
115
|
Активация - процесс, когда контейнер в ответ на запрос выдает экземпляр сервиса. При обработке запроса на получение сервиса контейнер создает контекст активации, всю работу по получению экземпляра сервиса выполняет дескриптор, которому передается контекст активации.
|
|
|
|
116
|
|
|
|
|
117
|
1. создается контекст активации,
|
|
|
|
118
|
2. ищется запись декриптора сервиса в контейнере,
|
|
|
|
119
|
3. дескриптору передается контекст активации,
|
|
|
|
120
|
4. дескриптор возвращает экземпляр сервиса.
|
|
|
|
121
|
|
|
|
|
122
|
В процессе создания экземпляра сервиса, дескриптор может использовать контекст активации для обращения к контейнеру, текущим сервисам, а также может использовать его для активации других дескрипторов.
|
|
|
|
123
|
|
|
|
|
124
|
### Типы активации сервисов
|
|
|
|
125
|
|
|
|
|
126
|
Тип активации относится к сервисам, в качестве которых были зарегистрированы либо типы, либо фабричные методы.
|
|
|
|
127
|
|
|
|
|
128
|
```ts
|
|
|
|
129
|
|
|
|
|
130
|
container.configure({
|
|
|
|
131
|
foo: {
|
|
|
|
132
|
$type: 'my/ServiceClass',
|
|
|
|
133
|
activation: 'container'
|
|
|
|
134
|
},
|
|
|
|
135
|
bar: {
|
|
|
|
136
|
$factory: () => {
|
|
|
|
137
|
return new ServiceClass();
|
|
|
|
138
|
},
|
|
|
|
139
|
activation: 'context'
|
|
|
|
140
|
}
|
|
|
|
141
|
});
|
|
|
|
142
|
|
|
|
|
143
|
```
|
|
|
|
144
|
|
|
|
|
145
|
#### call
|
|
|
|
146
|
|
|
|
|
147
|
Тип активации по-умолчанию, создается каждый раз, когда сервис запрашивается.
|
|
|
|
148
|
|
|
|
|
149
|
#### context
|
|
|
|
150
|
|
|
|
|
151
|
Экземпляр будет создан только один раз в рамках текущего контекста активации, т.е. при разрешении зависимостей будет использоваться все время один и тотже экземплар.
|
|
|
|
152
|
|
|
|
|
153
|
#### container
|
|
|
|
154
|
|
|
|
|
155
|
Будет создан только один экземпляр для контейнера, где сервис зарегистрирован. Созданный экземпляр сервиса будет автоматически очищен при освобождении контейнера.
|
|
|
|
156
|
|
|
|
|
157
|
```ts
|
|
|
|
158
|
container.cofigure({
|
|
|
|
159
|
db: {
|
|
|
|
160
|
$type : 'my/app/PgSqlDataContext',
|
|
|
|
161
|
activation: 'container',
|
|
|
|
162
|
params: {
|
|
|
|
163
|
host: 'localhost',
|
|
|
|
164
|
port: 5432
|
|
|
|
165
|
}
|
|
|
|
166
|
}
|
|
|
|
167
|
})
|
|
|
|
168
|
|
|
|
|
169
|
let db = container.getService<DataContext>('db');
|
|
|
|
170
|
|
|
|
|
171
|
let db2 = childContainer.getService<DataContext>('db');
|
|
|
|
172
|
|
|
|
|
173
|
//db === db2
|
|
|
|
174
|
|
|
|
|
175
|
container.dispose(); // will dispose db
|
|
|
|
176
|
|
|
|
|
177
|
```
|
|
|
|
178
|
|
|
|
|
179
|
#### hierarchy
|
|
|
|
180
|
|
|
|
|
181
|
Будет создан только один экземпляр для контейнера, который создал контекст активации. Созданный экземпляр червиса будет автоматически очищен при освобождении контейнера.
|
|
|
|
182
|
|
|
|
|
183
|
Данный вариант похож на тип активации `container`, но позволяет описать сервисы в родительском контейнере, а управлять временем жизни экземпляров этих сервисов при помощи дочерних контейнеров. Такой подход позволяет оптимизировать конфигурацию контейнеров.
|
|
|
|
184
|
|
|
|
|
185
|
```ts
|
|
|
|
186
|
container.cofigure({
|
|
|
|
187
|
db: {
|
|
|
|
188
|
$type : 'my/app/PgSqlDataContext',
|
|
|
|
189
|
activation: 'hierarchy',
|
|
|
|
190
|
params: {
|
|
|
|
191
|
host: 'localhost',
|
|
|
|
192
|
port: 5432
|
|
|
|
193
|
}
|
|
|
|
194
|
}
|
|
|
|
195
|
})
|
|
|
|
196
|
|
|
|
|
197
|
let db = container.getService<DataContext>('db');
|
|
|
|
198
|
|
|
|
|
199
|
let db2 = childContainer.getService<DataContext>('db');
|
|
|
|
200
|
|
|
|
|
201
|
//db !== db2
|
|
|
|
202
|
|
|
|
|
203
|
childContainer.dispose(); // will dispose db2, db will be left intact
|
|
|
|
204
|
|
|
|
|
205
|
```
No newline at end of file
|