本文是对上篇文章(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;
/**
* 重写()操作符用于在更新时调用所存储的回调
* @param old_val 旧值
* @param new_val 新值
*/
void operator()(const var::Base* old_val, const var::Base* new_val) {
if (cb_) cb_(old_val, new_val);
}
/**
* 返回自身Id
*/
inline Id id() const { return id_; }
private:
// 监听者id
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);
}
/**
* 反注册监听回调
* @param listener 监听回调对象
*/
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;
/**
* 重写()操作符用于在更新时调用所存储的回调
* @param old_val 旧值
* @param new_val 新值
*/
void operator()(const var::Base* old_val, const var::Base* new_val);
/**
* 返回自身Id
*/
inline Id id() const { return id_; }
private:
// 监听者id
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);
/**
* 使用Json对象来进行更新
*/
void Update(const nlohmann::json& json);
/**
* 注册回调函数, 用于普通函数
* @return 返回监听对象指针, 交给调用者控制生命周期
*/
Listener::Ptr RegisterListener(void (*func)(const T*, const T*));
/**
* 注册回调函数, 用于类成员函数
* @return 返回监听对象指针, 交给调用者控制生命周期
*/
template <class Class>
Listener::Ptr RegisterListener(void (Class::*func)(const T*, const T*), Class *c);
/**
* 反注册监听回调
* @param listener 监听回调对象
*/
void UnregisterListener(const Listener::Ptr& listener);
private:
// 存储以var::Base为基类的值
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;
/**
* 重写()操作符用于在更新时调用所存储的回调
* @param old_val 旧值
* @param new_val 新值
*/
void operator()(const var::Base* old_val, const var::Base* new_val);
/**
* 返回自身Id
*/
inline Id id() const { return id_; }
private:
// 监听者id
Id id_;
// 所储存的回调用于更新时调用
Callback cb_;
};
public:
explicit ObserverBaseObj(std::shared_ptr<var::Base> var)
: var_(std::move(var)) {}
virtual ~ObserverBaseObj() = default;
/**
* 将Json对象转换接口作为虚函数的原因如下:
* 1. 用于对接配置管理器的Json值更新, 需要继承的子类去确定对象并实现
* 2. 用于其他调用者可能会传入Json值进行更新
* @param json json值
*/
virtual void Update(const nlohmann::json& json) = 0;
protected:
/**
* 添加监听回调
* @param listener 监听者指针
*/
void AddListener(const Listener::Ptr& listener);
/**
* 移除指定的添加监听回调
* @param id 监听者指针
*/
void RemoveListener(const Listener::Ptr& listener);
/**
* 通知所有已注册回调进行更新
* @param new_val 新值
*/
void NotifyChange(const var::Base* new_val);
/**
* 返回保存的var
*/
inline std::shared_ptr<var::Base> var() const {
return var_;
}
private:
// 存储以var::Base为基类的值
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);
/**
* 使用Json对象来进行更新
*/
void Update(const nlohmann::json& json) override;
/**
* 注册回调函数, 用于普通函数
* @return 返回监听对象指针, 交给调用者控制生命周期
*/
Listener::Ptr RegisterListener(void (*func)(const T*, const T*));
/**
* 注册回调函数, 用于类成员函数
* @return 返回监听对象指针, 交给调用者控制生命周期
*/
template <class Class>
Listener::Ptr RegisterListener(void (Class::*func)(const T*, const T*), Class *c);
/**
* 反注册监听回调
* @param listener 监听回调对象
*/
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