【问题标题】:How do I design an Observer pattern with different types of notifications and observers?如何设计具有不同类型通知和观察者的观察者模式?
【发布时间】:2020-02-10 11:32:10
【问题描述】:

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

所有这些SketchbookEventObservers 都必须使用所发生事件的数据发出网络请求:

MouseClickObserver - 鼠标点击的坐标。

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

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

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

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

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

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

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

有更好的设计方法吗?

【问题讨论】:

    标签: c++ oop design-patterns


    【解决方案1】:

    一种方法是您可以从 EventData 类派生 MouseClickEventData、TouchEventData、TextChanged 类。并转换您的 EventData,这样您就可以为每个数据提供特定的类。

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

    【讨论】:

    • 这绝对是我想到的。我正在考虑 dynamic_cast 是否是解决这个问题的好方法,还是存在更优雅的设计解决方案。
    • 在某些时候你必须使用演员表。或者您可以使用通用数据表示,如 xml、json。
    • 这是否意味着 EventData 仅存在于 dynamic_cast 并且没有被覆盖的实际方法?它本身不提供任何多态性。这是好设计吗?
    • 好吧,它不需要覆盖一些东西。它允许您只有一个 OnEvent 函数。仍然可能有一些覆盖取决于您的要求。例如。 EventType EventData::GetType() 可以被覆盖
    【解决方案2】:

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

    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);
    

    【讨论】:

    • Visitor 是解决少量固定事件的好方法;所以它可以在这里工作。如果未来有可能出现新的事件类型,Visitor 就会成为维护问题。
    • @jaco0646 确实如此。如果有很多事件要观看,每个事件的事件处理程序接口是更好的选择。
    【解决方案3】:

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

    这是我最喜欢的选项,也是我多次提倡的选项,因为它维护了事件的类型安全。如果每个事件都不同,请不要通过同一个 API 处理所有事件。

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

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

    【讨论】:

      猜你喜欢
      • 2011-09-25
      • 1970-01-01
      • 2010-12-24
      • 1970-01-01
      • 2020-12-26
      • 1970-01-01
      • 1970-01-01
      • 2016-02-20
      • 2023-04-10
      相关资源
      最近更新 更多