往期本博主的 C++ 精讲优质博文可通过这篇导航进行查找:
👇👇👇👇👇
Lemo 的C++精华博文导航:进阶、精讲、设计模式文章全收录
👆👆👆👆👆
前言
前文中我们讲到,如何通过应用 Pimpl 模式进行接口类实现类的分离,来进行接口的设计。
没有读过前文的读者,可以通过这个链接去读下: 《深入 C++ 实践:通过 Pimpl 模式来谈接口类与实现类分离的设计原则》
但是,实际业务场景中是多变的,不可能通过一种方式适用所有场景。
现在我们就来看看一种比较常见的情况:
模块层/平台层代码已经完成了功能开发,但是业务比较复杂,或耦合较深,不便于修改现有接口暴露出来给外部模块使用。想基于已有的功能新增API接口暴露出来供外部使用。
那么对于这种问题,我们的解决思路是什么呢?
解决思路
对于这种问题,我们通常能够想到的是通过协议扩展的形式来解决。
通俗而言,怎么做呢?
封装的类与平台内部的类通过协议扩展形式进行关联,即封装的类和内部的类既不是派生关系,也不是通过指针指向的接口类实现类的关系,而是遵守一套相同的协议进行关联。
即:模块初始化时,内部的类通过协议扩展关联封装的类;模块卸载时,内部的类通过协议扩展与封装的类解除关联;
当调用封装的类时,实际上通过协议扩展去找到内部的内,然后进行相关业务逻辑的流转。
我们本着这样的思路来看一段示例代码:
示例代码
#include <iostream>
#include <map>
// 定义协议接口
class IProtocol {
public:
virtual void PerformAction() = 0;
virtual ~IProtocol() {}
};
// 内部类实现协议接口
class InternalClass : public IProtocol {
public:
void PerformAction() override {
std::cout << "InternalClass action performed." << std::endl;
}
};
// 协议扩展管理器
class ProtocolExtensionManager {
public:
void Register(const std::string& key, IProtocol* protocol) {
protocols[key] = protocol;
}
void Unregister(const std::string& key) {
protocols.erase(key);
}
IProtocol* GetProtocol(const std::string& key) {
auto it = protocols.find(key);
if (it != protocols.end()) {
return it->second;
}
return nullptr;
}
private:
std::map<std::string, IProtocol*> protocols;
};
// 封装类,用户将使用此类,它对内部类进行封装
class WrappedClass {
public:
WrappedClass(ProtocolExtensionManager& manager, const std::string& key)
: protocolManager(manager), protocolKey(key) {}
void DoSomething() {
IProtocol* internal = protocolManager.GetProtocol(protocolKey);
if (internal != nullptr) {
internal->PerformAction();
} else {
std::cout << "Action cannot be performed, no protocol associated." << std::endl;
}
}
private:
ProtocolExtensionManager& protocolManager;
std::string protocolKey;
};
// 示范如何使用上述类
int main() {
// 创建协议扩展管理器
ProtocolExtensionManager manager;
// 创建内部类并注册到管理器中
InternalClass internal;
manager.Register("InternalProtocol", &internal);
// 创建封装类,封装内部类的使用
WrappedClass wrapped(manager, "InternalProtocol");
// 使用封装类来执行操作
wrapped.DoSomething();
// 注销协议并清理
manager.Unregister("InternalProtocol");
return 0;
}
在这个示例中,InternalClass
实现了 IProtocol
。 WrappedClass
是用户互动的封装类,它通过 ProtocolExtensionManager
来调用指定的 InternalClass
实例。
当然,上述的封装还不够彻底。通常对于 WrappedClass
而言,设计者会进行隐藏一系列设计逻辑。那么对于这块,可以采用上文中的 Pimpl
模式,进行接口类和实现类的再次封装,进行信息的隐藏。
再次封装
class WrappedClass {
public:
WrappedClass(ProtocolExtensionManager& manager, const std::string& key)
{
APP_I(*new WrappedClassImp(manager, key));
}
WrappedClass::WrappedClass(WrappedClassImp& impl)
{
APP_I(impl);
}
WrappedClass::~WrappedClass()
{
APP_E();
}
void DoSomething() {
APP_D(WrappedClass);
d->DoSomething();
}
protected:
APP_DISABLE_COPY(WrappedClass);
APP_DECLARE_IMPL(WrappedClass);
};
class WrappedClassImp : public BaseImpl<WrappedClass> {
public:
APP_DEFINE_GET_IMPL_FUNC(WrappedClass);
WrappedClassImpl(ProtocolExtensionManager& manager, const std::string& key)
: protocolManager(manager), protocolKey(key) {}
void DoSomething() {
IProtocol* internal = protocolManager.GetProtocol(protocolKey);
if (internal != nullptr) {
internal->PerformAction();
} else {
std::cout << "Action cannot be performed, no protocol associated." << std::endl;
}
}
private:
ProtocolExtensionManager& protocolManager;
std::string protocolKey;
};
如此,再次封装之后,更多的信息被隐藏了。
总结
这类问题的解决思路有很多,在此先抛砖引玉出一种供大家参考。如果有更好的方式,欢迎留言讨论。