本文是对上篇文章(C++中配置转换设计)的运用
前置知识:模板,完美转发,智能指针,设计模式-监听模式
场景运用
在项目中有时需要对一些配置变量发生改动时进行回调监听,不仅发生在被其他调用者修改的场景,也可能存在对应的配置管理器需要监听文件变动来触发热更新这类的场景,所以我们希望:
- 当值发生更新时能通过绑定的回调得到新老值来触发对应的业务逻辑
- 可以由绑定的调用者来控制回调的生命周期
- 配置管理器可以通过传递对应的对象来触发转发和回调
如何设计
整体可以采用监听模式来设计,部分细节如下:
监听回调设计
对于监听回调来说,可以采用多种方法来注册对应的回调函数给观察者,但同时也需要使用者来对回调的生命周期进行控制,当然难免会出现生命周期控制不当的情况,所以这里采用更加优雅的方式来减少出现这种问题的情况
将监听回调视为一个观察者的子模块,因此可以在里面保存对应的回调函数以及自身的标识:
template<class T> class ObserverObj { public:
class Listener { public: using Id = size_t; using Callback = std::function<void(const var::Base*, const var::Base*)>; public: explicit Listener(Id id, Callback cb) : id_(id), cb_(std::move(cb)) {} ~Listener() = default;
void operator()(const var::Base* old_val, const var::Base* new_val) { if (cb_) cb_(old_val, new_val); }
inline Id id() const { return id_; } private: Id id_; Callback cb_; }; };
|
观察者设计
那么对于观察者来说,可以使用 std::map
对监听回调对象进行存储和管理:
template<class T> class ObserverObj { public:
class Listener { public: ... using Ptr = std::shared_ptr<Listener>; using WPtr = std::weak_ptr<Listener>; }; private: std::map<Listener::Id, Listener::WPtr> listeners_; };
|
由于需要将回调对象的生命周期交予调用者管理,所以注册接口的返回值和反注册接口的参数均为回调对象。对于注册接口的设计,为了调用者在不需要通过 std::bind
等方法进行绑定后再传递进来,可以考虑传递对应的函数等指针在内部进一步封装:
template<class T> class ObserverObj { ... public:
inline typename Listener::Ptr RegisterListener(void (*func)(const T*, const T*)) { auto cb = [func](const var::Base* old_val, const var::Base* new_val) { (*func)(dynamic_cast<const T*>(old_val), dynamic_cast<const T*>(new_val)); }; return RegisterListenerImpl(cb); }
template <class Class> inline typename Listener::Ptr RegisterListener(void (Class::*func)(const T*, const T*), Class *c) { auto cb = [c, func](const var::Base* old_val, const var::Base* new_val) { (c->*func)(dynamic_cast<const T*>(old_val), dynamic_cast<const T*>(new_val)); }; return RegisterListenerImpl(cb); }
inline void UnregisterListener(const typename Listener::Ptr& listener) { listeners_.erase(listener->id()); } private:
typename Listener::Ptr RegisterListenerImpl(const typename Listener::Callback& cb) { static std::atomic_uint64_t id = 0; auto listener = std::make_shared<Listener>(id++, cb); listeners_.insert({listener->id(), listener}); return listener; } };
|
总体定义
因此总体定义如下:
template<class T> class ObserverObj { public:
class Listener { public: 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*)>; public: explicit Listener(Id id, Callback cb) : id_(id), cb_(std::move(cb)) {} ~Listener() = default;
void operator()(const var::Base* old_val, const var::Base* new_val);
inline Id id() const { return id_; } private: Id id_; Callback cb_; }; public: template <typename ...Args> explicit ObserverObj(Args&&... args) : var_(std::make_shared<T>(std::forward<Args>(args)...)) {} explicit ObserverObj(const T& val) : var_(std::make_shared<T>(val)) {} ~ObserverObj() = default;
void Update(const T& val);
void Update(const nlohmann::json& json);
Listener::Ptr RegisterListener(void (*func)(const T*, const T*));
template <class Class> Listener::Ptr RegisterListener(void (Class::*func)(const T*, const T*), Class *c);
void UnregisterListener(const Listener::Ptr& listener); private: std::shared_ptr<var::Base> var_; std::map<typename Listener::Id, typename Listener::WPtr> listeners_; };
|
使用 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 { public:
class Listener { public: 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*)>; public: explicit Listener(Id id, Callback cb) : id_(id), cb_(std::move(cb)) {} ~Listener() = default;
void operator()(const var::Base* old_val, const var::Base* new_val);
inline Id id() const { return id_; } private: Id id_; Callback cb_; }; public: explicit ObserverBaseObj(std::shared_ptr<var::Base> var) : var_(std::move(var)) {} virtual ~ObserverBaseObj() = default;
virtual void Update(const nlohmann::json& json) = 0; protected:
void AddListener(const Listener::Ptr& listener);
void RemoveListener(const Listener::Ptr& listener);
void NotifyChange(const var::Base* new_val);
inline std::shared_ptr<var::Base> var() const { return var_; } private: std::shared_ptr<var::Base> var_; std::map<Listener::Id, Listener::WPtr> listeners_; };
|
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> class ObserverObj : protected ObserverBaseObj { public: template <typename ...Args> explicit ObserverObj(Args&&... args) : ObserverBaseObj(std::make_shared<T>(std::forward<Args>(args)...)) {} explicit ObserverObj(const T& val) : ObserverBaseObj(std::make_shared<T>(val)) {} ~ObserverObj() = default;
void Update(const T& val);
void Update(const nlohmann::json& json) override;
Listener::Ptr RegisterListener(void (*func)(const T*, const T*));
template <class Class> Listener::Ptr RegisterListener(void (Class::*func)(const T*, const T*), Class *c);
void UnregisterListener(const Listener::Ptr& listener); };
|
因此整体 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