如何设计具有不同类型的通知和观察器的观察者模式?

How do I design an Observer pattern with different types of notifications and observers?

本文关键字:通知 观察 观察者模式 同类型      更新时间:2023-10-16

场景:假设我有一个基类SketchbookEventObserver和派生类MouseClickObserverTouchObserverTextChangedObserver

所有这些SketchbookEventObserver都必须发出网络请求,其中包含有关所发生事件的数据:

MouseClickObserver- 鼠标单击的坐标。

TouchObserver- 触摸的坐标和持续时间。

TextChangedObserver- 带有文本框标识符的新旧文本。

所有这些观察者都在UIEventRegistry类中注册。当事件发生时,它会在每个观察者上调用OnEvent,并作为参数传递:

  1. 事件类型 - 鼠标单击/触摸/文本已更改。这由 ID 表示。
  2. 有关事件的数据 - 如上所述。每种类型的事件都有不同类型的数据

但是,我不能在每个派生类中使用不同的输入参数重写OnEvent。如果我有输入参数泛型和多态,比如EventData带有GetData()函数,我仍然需要在派生类中覆盖GetData()EventData,这将具有不同的返回值。这也是不可能的。

另一种选择是这些观察者之间没有任何继承,并将它们视为单独的实体。EventRegistry将有一个每种类型的观察者的数组/列表,其中它们的类型是已知的,然后为鼠标单击事件调用mMouseClickObservers[i].OnEvent(),为触摸事件调用mTouchObservers[i].OnEvent(),依此类推。

但这意味着EventRegistry需要了解具体类的知识,如果EventRegistry是不同库/包的一部分,则需要公开这些类。

有没有更好的方法来设计这个?

一种方法是从EventData类派生MouseClickEventData,TouchEventData,TextChanged类。并强制转换事件数据,以便每个数据都有特定的类。

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();
}
....
}

为了根据观察者的类型和被观察的事物来改变观察者的行为,我们需要使用双重调度。访客模式使用双重调度来允许一组访客观察一组事件。我不会在这里提供实现,但伪代码类似于:

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及其UI框架所采用的方法。例如:

class Component:
void registerMouseClickListener(MouseClickListener listener):
...
void registerTouchListener(TouchListener listener):
...
interface MouseClickListener:
void handle(MouseClickEvent event);
interface TouchListener:
void handle(TouchEvent event);

另一种选择是这些观察者之间没有任何继承,并将它们视为单独的实体。

这是我最喜欢的选项,也是我多次提倡的选项,因为它可以保持事件的类型安全性。如果每个事件都不同,请不要通过同一 API 处理它们。

但这意味着EventRegistry需要了解具体的类......

MouseClickObserverTouchObserverTextChangedObserver都应该是抽象的。现在每个抽象可能只有一个实现;但设计不应该强制执行这一点。