Суть известного наблюдателя / наблюдаемого паттерна состоит в том, что у вас есть наблюдаемый объект, который производит события различного рода, и один или несколько объектов- наблюдателей, которые регистрируют себя как заинтересованные в уведомлении, когда происходят эти события.
Конечно, мы представляем каждый тип события как тип, обычно класс, хотя ничто не мешает нам использовать тип интерфейса в качестве типа события.
Например:
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 events userPersistence.addObserver( (Created<User> userCreated) => print( "new user created: " + userCreated.entity.name)); //observe User creation and deletion events userPersistence.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 . |