Суть известного наблюдателя / наблюдаемого паттерна состоит в том, что у вас есть наблюдаемый объект, который производит события различного рода, и один или несколько объектов- наблюдателей, которые регистрируют себя как заинтересованные в уведомлении, когда происходят эти события.
Конечно, мы представляем каждый тип события как тип, обычно класс, хотя ничто не мешает нам использовать тип интерфейса в качестве типа события.
Например:
|
1
2
|
class Started() {}class Stopped() {} |
Тип события может быть даже общим:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
class Created<out Entity> (shared Entity entity) given Entity satisfies Object { string => "Created[``entity``]";}class Updated<out Entity> (shared Entity entity) given Entity satisfies Object { string => "Updated[``entity``]";}class Deleted<out Entity> (shared Entity entity) given Entity satisfies Object { string => "Deleted[``entity``]";} |
Конечно, у нас есть мощные механизмы для абстрагирования типов событий, например:
|
1
2
3
4
5
|
alias Lifecycle<Entity> given Entity satisfies Object => Created<Entity> | Updated<Entity> | Deleted<Entity>; |
Наблюдатель, как правило, по сути является не чем иным, как функцией, которая принимает определенный тип события в качестве параметра.
Например, эта анонимная функция наблюдает за созданием User s:
|
1
2
|
(Created<User> userCreated) => print("new user created: " + userCreated.entity.name) |
Эта анонимная функция наблюдает события жизненного цикла любого вида объекта:
|
1
2
|
(Lifecycle<Object> event) => print("something happened: " + event) |
Типы объединения и пересечения дают нам хороший способ выразить соединение и дизъюнкцию типов событий:
|
1
2
3
4
5
6
7
8
9
|
void (Created<User>|Deleted<User> userEvent) { switch (userEvent) case (is Created<User>) { print("user created: " + userEvent.entity.name); } case (is Deleted<User>) { print("user deleted: " + userEvent.entity.name); }} |
Теперь здесь мы можем сделать что-то действительно милое. Как правило, в других языках наблюдаемый объект предоставляет различные операции регистрации наблюдателя, по одной для каждого типа события, которое производит объект. Мы собираемся определить универсальный класс Observable который работает для любого типа события и использует усовершенствованные обобщенные типы для отображения событий в функции наблюдателя.
|
1
2
3
4
|
shared class Observable<in Event>() given Event satisfies Object { ...} |
Параметр типа Event захватывает различные виды событий, которые создает этот объект, например, Observable<Lifecycle<User>> создает события типа Created<User> , Updated<User> и Deleted<User> .
Нам нужен список для хранения наблюдателей в:
|
1
|
value listeners = ArrayList<Anything(Nothing)>(); |
Здесь Anything(Nothing) является супертипом любой функции с одним параметром.
Метод addObserver() регистрирует функцию наблюдателя в Observable :
|
1
2
3
4
|
shared void addObserver<ObservedEvent> (void handle(ObservedEvent event)) given ObservedEvent satisfies Event => listeners.add(handle); |
Этот метод принимает функции наблюдателя только для некоторого подмножества событий, фактически произведенных Observable . Это ограничение обеспечивается верхней границей given ObservedEvent satisfies Event .
Метод raise() производит событие:
|
1
2
3
4
|
shared void raise<RaisedEvent>(RaisedEvent event) given RaisedEvent satisfies Event => listeners.narrow<Anything(RaisedEvent)>() .each((handle) => handle(event)); |
Опять же, верхняя граница обеспечивает, что этот метод принимает только те объекты событий, которые имеют тип события, созданный Observable .
Этот метод использует новый метод narrow() Iterable в Цейлоне 1.2, чтобы отфильтровать функции-наблюдатели, которые не принимают возбужденный тип события. Этот метод реализован с использованием усовершенствованных обобщений. Вот его определение в Iterable<Element> :
|
1
2
|
shared default {Element&Type*} narrow<Type>() => { for (elem in this) if (is Type elem) elem }; |
То есть, если у нас есть поток Element s, и мы вызываем narrow<Type>() , явно передавая произвольный тип Type , то мы возвращаем поток всех элементов исходного потока, которые являются экземплярами Type . Это, естественно, поток Element&Type s.
Теперь, наконец, если мы определим экземпляр Observable :
|
01
02
03
04
05
06
07
08
09
10
11
|
object userPersistence extends Observable<Lifecycle<User>>() { shared void create(User user) { ... //raise an event raise(Created(user)); } ...} |
Затем мы можем зарегистрировать наблюдателей для этого объекта следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
//observe User creation eventsuserPersistence.addObserver( (Created<User> userCreated) => print("new user created: " + userCreated.entity.name));//observe User creation and deletion eventsuserPersistence.addObserver( void (Created<User>|Deleted<User> userEvent) { switch (userEvent) case (is Created<User>) { print("user created: " + userEvent.entity.name); } case (is Deleted<User>) { print("user deleted: " + userEvent.entity.name); }}); |
Обратите внимание, что с типами объединения и пересечения, подтипами и дисперсией мы находимся с мощным языком выражений, позволяющим точно определить, какие именно типы событий нас интересуют, с учетом безопасности типов, прямо в списке параметров функции наблюдателя.
Для записи вот полный код Observable :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
shared class Observable<in Event>() given Event satisfies Object { value listeners = ArrayList<Anything(Nothing)>(); shared void addObserver<ObservedEvent> (void handle(ObservedEvent event)) given ObservedEvent satisfies Event => listeners.add(handle); shared void raise<RaisedEvent>(RaisedEvent event) given RaisedEvent satisfies Event => listeners.narrow<Anything(RaisedEvent)>() .each((handle) => handle(event));} |
Наконец, предостережение: точный код, приведенный выше, не компилируется в Ceylon 1.1, потому что метод narrow() является новым и из-за исправленной ошибки в проверке типов. Но это будет работать в предстоящем выпуске Цейлона 1.2.
| Ссылка: | Уникальный подход к наблюдателю / наблюдаемой модели на Цейлоне от нашего партнера JCG Гэвина Кинга в блоге команды Ceylon Team . |