插件式开发是为了可扩展,同时减少对系统内其他组件的认知负担,只需要遵循特定的协议,开发好当前这个插件即可。
例如一个系统中可能存在以下功能: System={编解码;配置读写;缓存;寻址}。随着功能的迭代,系统的功能越来越多,单个功能的实现方式也越来越多样,例如缓存可以使用memcache、redis,配置读写可能是json、yaml等格式。那个这个系统一个良好的结构图可能是:
插件基类定义了所有插件需要上报的一些基础信息和插件类型。
enum class PluginType {
kUnknown, // 未知类型
kCodec, // 编解码插件
kConfig, // 配置读写组件
kCache, // 缓存组件
kNaming, // 寻址组件
};
class Plugin {
public:
virtual std::string Name() = 0;
virtual std::string Version() = 0;
virtual PluginType Type() = 0;
};
我们定义了一个缓存类插件,包含一些基础的读写接口,所有缓存插件必须实现这些功能
class CachePlugin : public Plugin {
public:
PluginType Type() override { return PluginType::kCache; }
// 对于缓存组件我们规定以下几个动作:
// Init:初始化
// Destry:回收资源
// Set:写缓存
// Get:读缓存
virtual bool Init(const std::string &path) = 0;
virtual void Destry() = 0;
virtual bool Set(const std::string &key, const std::string &val) = 0;
virtual bool Get(const std::string &key, std::string &val) = 0;
};
子类按照父类接口去实现对应功能。
class Redis : public CachePlugin {
public:
std::string Name() override { return "RedisPlugin"; }
std::string Version() override { return "Redis5.0"; }
bool Init(const std::string &path) override {
// ...
return true;
}
void Destry() override {}
bool Set(const std::string &key, const std::string &val) override {
// ...
return true;
}
bool Get(const std::string &key, std::string &val) override {
// ...
return true;
}
};
工厂通过读取配置文件实例化具体的缓存对象,存储到plugins_中,同时提供Get方法,以插件名获取插件。在实际使用的时候,通过插件名获取到不同的缓存插件,执行不同的动作。
/*
cache:
- redis
name: redis_cache
address: xx
code: xx
- memcache
name:
*/
class CacheFactory {
public:
using CachePluginType = std::shared_ptr<CachePlugin>;
bool LoadAllPlugins(const std::string &config) {
// 读取配置信息,将不同配置的redis、memcache实例化后插入到map中
for(...){
CachePluginType plugin;
if(plugin->Init(path)){
plugins_.emplace(name, plugin);
}
}
}
CachePluginType Get(const std::string &name) const {
auto itr = plugins_.find(name);
return itr == plugins_.end() ? nullptr : plugins_.at(name);
}
private:
std::unordered_map<std::string, CachePluginType> plugins_;
};
插件化开发,可以利用C++的多态实现,基类定义接口,子类定义具体的行为,工厂负责插件的管理。当然,向工厂中注册的行为也可以进一步写成宏,更方便使用。