本文是对上篇文章(C++中配置转换设计)的运用
前置知识:模板,完美转发,智能指针,设计模式-监听模式
场景运用
在项目中有时需要对一些配置变量发生改动时进行回调监听,不仅发生在被其他调用者修改的场景,也可能存在对应的配置管理器需要监听文件变动来触发热更新这类的场景,所以我们希望:
- 当值发生更新时能通过绑定的回调得到新老值来触发对应的业务逻辑
- 可以由绑定的调用者来控制回调的生命周期
- 配置管理器可以通过传递对应的对象来触发转发和回调
如何设计
整体可以采用监听模式来设计,部分细节如下:
监听回调设计
对于监听回调来说,可以采用多种方法来注册对应的回调函数给观察者,但同时也需要使用者来对回调的生命周期进行控制,当然难免会出现生命周期控制不当的情况,所以这里采用更加优雅的方式来减少出现这种问题的情况
将监听回调视为一个观察者的子模块,因此可以在里面保存对应的回调函数以及自身的标识:
template<class T> |
观察者设计
那么对于观察者来说,可以使用 std::map 对监听回调对象进行存储和管理:
template<class T> |
由于需要将回调对象的生命周期交予调用者管理,所以注册接口的返回值和反注册接口的参数均为回调对象。对于注册接口的设计,为了调用者在不需要通过 std::bind 等方法进行绑定后再传递进来,可以考虑传递对应的函数等指针在内部进一步封装:
template<class T> |
总体定义
因此总体定义如下:
template<class T> |
使用 UML 可以表示如下:
classDiagram
direction LR
class ObserverObj~T~ {
+ Update(const T& val)
+ Update(const nlohmann::json& json)
+ RegisterListener(void (*func)(const T*, const T*)) Listener::Ptr
+ RegisterListener~Class~(void (Class::*func)(const T*, const T*), Class *c) Listener::Ptr
+ UnregisterListener(const Listener::Ptr& listener)
- var_ : std::shared_ptr~T~
- listeners_ : std::map~Listener::Id, Listener::WPtr~
}
class Listener {
+ operator()(const var::Base* old_val, const var::Base* new_val)
+ id() : Id
- id_ : Id
- cb_ : Callback
}
ObserverObj*--Listener : << 监听回调子模块 >>
note for Listener "using Ptr = std::shared_ptr<Listener>
using WPtr = std::weak_ptr<Listener>
using Id = size_t;
using Callback = std::function<void(const var::Base*, const var::Base*)>"
问题?
但这样的定义会有问题,当我们需要使用容器存储以及管理对应的 ObserverObj 时,需要明确类型,但是即使在明确类型的情况下也不能使用同一个容器进行存储,因为不同类型的大小会导致类的内存布局发生变化,所以这样不能很好的对接以后的配置管理器
改进设计
因此可以增加一个非模板类来作为模板类的基类,同时将 Json 对象的 Update() 作为基类的接口,这样继承的模板类就只用于处理类型转换,基类进行值和监听回调的存储和管理:
class ObserverBaseObj { |
UML 所示:
classDiagram
direction LR
class ObserverBaseObj {
+ Update(const nlohmann::json& json)*
# AddListener(const Listener::Ptr& listener)
# RemoveListener(const Listener::Ptr& listener)
# NotifyChange(const var::Base* new_val)
# var() std::shared_ptr~var::Base~
- var_ : std::shared_ptr~var::Base~
- listeners_ : std::map~Listener::Id, Listener::WPtr~
}
class Listener {
+ operator()(const var::Base* old_val, const var::Base* new_val)
+ id() : Id
- id_ : Id
- cb_ : Callback
}
ObserverBaseObj*--Listener : << 监听回调子模块 >>
note for Listener "using Ptr = std::shared_ptr<Listener>
using WPtr = std::weak_ptr<Listener>
using Id = size_t;
using Callback = std::function<void(const var::Base*, const var::Base*)>"
那么 ObserverObj 就可以继承 ObserverBaseObj 进行实现:
template<class T> |
因此整体 UML 如下:
classDiagram
direction LR
class ObserverBaseObj {
+ Update(const nlohmann::json& json)*
# AddListener(const Listener::Ptr& listener)
# RemoveListener(const Listener::Ptr& listener)
# NotifyChange(const var::Base* new_val)
# var() std::shared_ptr~var::Base~
- var_ : std::shared_ptr~var::Base~
- listeners_ : std::map~Listener::Id, Listener::WPtr~
}
class Listener {
+ operator()(const var::Base* old_val, const var::Base* new_val)
+ id() : Id
- id_ : Id
- cb_ : Callback
}
class ObserverObj~T~ {
+ Update(const T& val)
+ RegisterListener(void (*func)(const T*, const T*)) Listener::Ptr
+ RegisterListener~Class~(void (Class::*func)(const T*, const T*), Class *c) Listener::Ptr
+ UnregisterListener(const Listener::Ptr& listener)
}
ObserverBaseObj <|-- ObserverObj~T~ : 实现
ObserverBaseObj*--Listener : << 监听回调子模块 >>
note for Listener "using Ptr = std::shared_ptr<Listener>
using WPtr = std::weak_ptr<Listener>
using Id = size_t;
using Callback = std::function<void(const var::Base*, const var::Base*)>"
完整实现
源码定义:tools/include/var.hpp at main · zyxeeker/tools · GitHub
测试用例:tools/tests/test_var.cpp at main · zyxeeker/tools · GitHub