Как мне создать шаблон Observer с различными типами уведомлений и наблюдателей? - PullRequest
1 голос
/ 10 февраля 2020

Сценарий: скажем, у меня есть базовый класс SketchbookEventObserver и производные классы MouseClickObserver, TouchObserver и TextChangedObserver.

Все эти SketchbookEventObserver s должны сделать сетевой запрос с данными о произошедшем событии:

MouseClickObserver - координаты щелчка мышью.

TouchObserver - координаты и длительность касания.

TextChangedObserver - старое и новый текст с идентификатором текстового поля.

Все эти наблюдатели зарегистрированы в классе UIEventRegistry. Когда происходит событие, оно будет вызывать OnEvent для каждого наблюдателя и передавать в виде параметров:

  1. Тип события - щелчок мышью / касание / текст изменен. Это представляется идентификатором.
  2. Данные о событии - как описано выше. Каждый тип события будет иметь разные типы данных .

Однако я не могу переопределить OnEvent с различными входными параметрами в каждом из производных классов. Если у меня есть входной параметр generi c и polymorphi c, скажем, EventData с функцией GetData(), мне все равно придется переопределить GetData() в производных классах EventData, которые будут иметь разные возвращаемые значения , Это также невозможно.

Другой вариант - не иметь наследства между этими наблюдателями и рассматривать их как отдельные объекты. EventRegistry будет иметь массив / список наблюдателей каждого типа, где их типы известны, затем вызвать mMouseClickObservers[i].OnEvent() для события щелчка мыши, mTouchObservers[i].OnEvent() для события касания и т. Д.

Но это означает, что EventRegistry потребуется знание о конкретных классах, которые должны быть опубликованы, если EventRegistry является частью другой библиотеки / пакета.

Есть ли лучший способ создать это

Ответы [ 3 ]

1 голос
/ 10 февраля 2020

Чтобы изменить поведение наблюдателей в зависимости от типа наблюдателя и наблюдаемой вещи, нам нужно использовать double-dispatch . Шаблон Visitor использует двойную диспетчеризацию, чтобы позволить посетителям наблюдать за набором событий. Я не буду приводить здесь реализацию, но psuedocode будет выглядеть следующим образом:

interface SketchbookEventObserver:
    void handle(MouseClickEvent event);
    void handle(TouchEvent event);

interface SketchbookEvent:
    void accept(SketchbookEventObserver observer);

class MouseClickObserver implements SketchbookEventObserver:

    void handle(MouseClickEvent event):
        ...

    void handle(TouchEvent event):
        ...

interface MouseClickEvent implements SketchbookEvent:

    void accept(SketchbookEventObserver observer):
        observer.handle(this);

Альтернативой является регистрация прослушивателя для каждого типа события. Это подход, принятый Java с его структурами пользовательского интерфейса. Например:

class Component:

    void registerMouseClickListener(MouseClickListener listener):
        ...

    void registerTouchListener(TouchListener listener):
        ...

interface MouseClickListener:
    void handle(MouseClickEvent event);

interface TouchListener:
    void handle(TouchEvent event);
1 голос
/ 10 февраля 2020

Другой вариант - не иметь наследства между этими наблюдателями и рассматривать их как отдельные объекты.

Это мой любимый вариант, который я отстаивал много раз потому что он поддерживает безопасность типов событий. Если каждое событие отличается, не обрабатывайте их все через один и тот же API.

Но это означает, что EventRegistry потребуется знание о конкретных классах ...

MouseClickObserver, TouchObserver и TextChangedObserver должны быть абстракциями. Теперь у вас может быть только одна реализация каждой абстракции; но дизайн не должен обеспечивать это.

1 голос
/ 10 февраля 2020

Одним из способов является получение классов MouseClickEventData, TouchEventData, TextChanged из класса EventData. И приведите свои EventData, чтобы у вас были определенные c классы для каждого из данных.

OnEvent(EventData *data) {
    if (data->type == MOUSE_CLICK) {
        MouseClickEventData *mData = dynamic_cast<MouseClickEventData*>(data); 
        // use mData->getCoordinates();
    }
    if (data->type == TEXT_CHANGED) {
        TextChangedEventData *tData = dynamic_cast<TextChangedEventData*>(data); 
        // use tData->getNewText();
    }
    ....
}
...