# Mixins ## 目录 - [介绍](#description) - [定义一个 mixin](#define-a-mixin) - [将 mixin 注入到 EventDispatcher](#inject-mixins-to-eventdispatcher) - [可选拦截点(Optional interceptor points)](#optional-interceptor-points) - [MixinFilter](#mixin-filter) - [公共类型](#public-types) - [函数](#functions) - [MixinFilter 示例代码](#sample-code-for-mixinfilter) ## 介绍 Mixin 用于向 EventDispatcher / EventQueue 继承层次中注入代码,以扩展它们的功能。本文将以 EventDispatcher 为例进行介绍,EventQueue 的 mixin 相关用法与 EventDispatcher 完全一致。 原有的 EventDispatcher 继承层次如下: ``` EventDispatcher <- EventDispatcherBase ``` `EventDispatcher` 是一个空类,其继承来自 `EventDispatcherBase` 的所有函数和数据。 在注入两个 mixin 类 A 、 B 后,层次如下: ``` EventDispatcher <- MixinA <- MixinB <- EventDispatcherBase ``` mixin 能够使用所有 EventDispatcherBase 中的所有 public 和 protected 成员(类型、函数和数据)。mixin 中的所有公共成员对用户来说都是可见、可用的。 ## 定义一个 mixin 一个 mixin 是有着一个模板参数的模板类。mixin 必须继承其模板参数。一个典型的 mixin 如下: ```cpp template class MyMixin : public Base { }; ``` ## 将 mixin 注入到 EventDispatcher 想要启用 mixin ,需要将 mixin 加入到策略类的 `Mixins` 类型中。例如,要启动 `MixinFilter` ,可以像下面这样来定义调度器 ```cpp struct MyPolicies { using Mixins = eventpp::MixinList; }; eventpp::EventDispatcher dispatcher; ``` 若有多个 mixin ,像是 `using Mixins = eventpp::MixinList` ,则继承层次如下: ``` EventDispatcher <- MixinA <- MixinB <- MixinC <- EventDispatcherBase ``` 最前面的排在继承层次的最底部 ## 可选拦截点( Optional interceptor points ) 一个 mixin 可以有一些具有特殊名称的函数,这些函数会被在特定的时候调用。这些特殊的函数必须是 public 的。当前只有一个特殊函数,如下: ```cpp template bool mixinBeforeDispatch(Args && ...args) const; ``` `mixinBeforeDispatch` 会在 EventDispatcher、EventQueue 开始调度事件之前被调用,其接收所有传给 EventDispatcher::dispatch 的参数,除了所有的参数都是以左值引用的方式传递的,无论它们在回调函数原型中是否被引用(无法修改 const 引用)。所以该函数能够修改实参,让后续的监听器都看到修改后的值。 因此该函数可以修改实参,所有的监听器看到的参数都是修改后的值。 该函数返回 `true` 时将继续调度,返回 `false` 时会停止继续调度。 对于多 mixin 的情况,该函数会被按照这些 mixin 出现在策略类的 MixinList 中的顺序来执行。 ## MixinFilter MixinFilter 可以在调度开始前过滤或修改所有的事件。 `MixinFilter::appendFilter(filter)` 向调度器中添加新的事件过滤器。`filter` 接收参数的类型是带有左值引用类型的回调函数原型。 所有的事件都会在执行任何监听器前触发事件过滤器的执行。 因为所有的实参都是以左值引用的形式传递的,因此事件过滤器也能修改这些实参,无论这些实参是否被在回调原型中引用(当然,const 引用是改不了的)。 下面的表格展示了事件过滤器如何接收实参 | 回调原型的实参类型 | 过滤器收到的实参类型 | 过滤器是否能修改实参? | 备注 | | ------------------------ | -------------------------- | ---------------------- | ------------------------- | | int, const int | int &, int & | 是 | 抛弃 const 值的不变性 | | int &, std::string & | int &, std::string & | 是 | | | const int &, const int * | const int &, const int * & | 否 | 必须保持引用/指针的不变性 | 事件过滤器强大且实用,下面是一些用例示例 1. 捕捉并阻塞所有感兴趣的事件。例如,在一个 GUI 窗口系统中,所有的窗口都能接收到鼠标的点击事件。然而,当一个窗口正在被鼠标拖拽时,只有被拖拽的窗口才应该能接收到鼠标事件,即使鼠标正在其他窗口上移动也应如此。所以当开始拖动时,窗口可以添加一个过滤器,该过滤器重定向所有发到窗口的鼠标事件,阻止其他窗口的监听器获得鼠标事件,但扔放行鼠标事件外的所有其他事件。 2. 设置捕捉所有事件的监听器。例如,在一个电话本系统中,系统根据动作(action)来发送事件(删除添加电话号、查找电话号等)。如果想要在该系统中实现这样的一个模块:其只需要取电话号的区号,而不关心具体发生了的动作,该怎么做呢?一种方法是让该模块可以监听所有的事件(添加、删除、查找),但这种方法比较脆弱——若添加了一种新的动作事件,而忘记了为该模块定义相应的监听逻辑,就会导致出现未定义的行为。更好的方法是为该模块添加一个过滤器,并在过滤器中检查区号。 ### 公共类型 `FilterHandle`:appendFilter 返回的句柄类型。过滤器句柄可用于移除过滤器。可以通过将 FilterHandle 实例转换为 bool 类型来检查其是否为空,当值为 false 时,该句柄为空。`FilterHandle` 是可拷贝的。 ### 函数 ```cpp FilterHandle appendFilter(const Filter & filter); ``` 将 `filter` 添加进调度器。 该函数将返回一个可用于 `removeFilter` 的过滤器句柄 ```cpp bool removeFilter(const FilterHandle & filterHandle); ``` 从调度其中移除一个过滤器。 当过滤器被成功移除时返回 true。 ### MixinFilter 示例代码 #### 代码 ```cpp struct MyPolicies { using Mixins = eventpp::MixinList; }; eventpp::EventDispatcher dispatcher; dispatcher.appendListener(3, [](const int e, const int i, const std::string & s) { std::cout << "Got event 3, i was 1 but actual is " << i << " s was Hello but actual is " << s << std::endl ; }); dispatcher.appendListener(5, [](const int e, const int i, const std::string & s) { std::cout << "Shout not got event 5" << std::endl; }); // 添加三个事件过滤器. // 第一个过滤器将输入的实参改为其他值,然后后续的过滤器及监听器将看到修改后的值 dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { std::cout << "Filter 1, e is " << e << " passed in i is " << i << " s is " << s << std::endl; i = 38; s = "Hi"; std::cout << "Filter 1, changed i is " << i << " s is " << s << std::endl; return true; }); // 第二个过滤器将所有 5 事件都过滤出来。因此监听事件 5 的过滤器都不会被触发。 // 第三个过滤器也不会在事件 5 触发 dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { std::cout << "Filter 2, e is " << e << " passed in i is " << i << " s is " << s << std::endl; if(e == 5) { return false; } return true; }); // 第三个过滤器只打印输入的实参 dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { std::cout << "Filter 3, e is " << e << " passed in i is " << i << " s is " << s << std::endl; return true; }); // 调度所有事件,第一个实参总是事件类型 dispatcher.dispatch(3, 1, "Hello"); dispatcher.dispatch(5, 2, "World"); ``` 输出 > Filter 1, e is 3 passed in i is 1 s is Hello > Filter 1, changed i is 38 s is Hi > Filter 2, e is 3 passed in i is 38 s is Hi > Filter 3, e is 3 passed in i is 38 s is Hi > Got event 3, i was 1 but actual is 38 s was Hello but actual is Hi > Filter 1, e is 5 passed in i is 2 s is World > Filter 1, changed i is 38 s is Hi > Filter 2, e is 5 passed in i is 38 s is Hi