C++ 含共有与独有方法的两个类如何编写整洁接口
最优实现方案(C++17及以上)
直接使用std::variant+std::visit实现,完全不需要臃肿的基类、不需要全局维护mode标记重复写分支判断,也不用给不支持的方法补无意义的空实现,类型安全可在编译期得到保证。
首先保持两个原始业务类的代码不变,不需要强行调整继承关系:
class Red{ public: void funcA() { /* 原有业务逻辑 */ } void funcC() { /* 原有业务逻辑 */ } }; class Blue{ public: void funcB() { /* 原有业务逻辑 */ } void funcC() { /* 原有业务逻辑 */ } };
编写统一的接口包装类,内部持有std::variant<Red, Blue>类型的实例,所有调用逻辑收敛在接口内部:
#include <variant> class ColourInterface { private: std::variant<Red, Blue> obj; public: // 初始化接口,传入mode决定持有的实例类型 void init(int mode) { if (mode == 0) { obj = Red{}; } else { obj = Blue{}; } } // 共有方法:两种实例都支持,直接统一分发调用 void funcC() { std::visit([](auto& instance) { instance.funcC(); }, obj); } // Red独有方法:仅当持有的是Red实例时才执行 void funcA() { if (auto* red_ptr = std::get_if<Red>(&obj)) { red_ptr->funcA(); } // 持有的是Blue实例时直接跳过,符合需求 } // Blue独有方法:仅当持有的是Blue实例时才执行 void funcB() { if (auto* blue_ptr = std::get_if<Blue>(&obj)) { blue_ptr->funcB(); } } };
这种实现的优势:
- 不需要侵入修改原有Red、Blue类的代码,不需要强行抽象公共基类,完全避免基类塞满独有方法的臃肿问题
- 所有类型判断逻辑收敛在接口类内部,上层调用方完全不需要感知当前持有的是哪类实例
- 类型校验由编译器完成,不会出现空指针调用、方法不存在的运行时错误
- 后续新增其他颜色类(比如Green),只需要给variant添加对应类型,给类的独有方法补充一个类型判断分支即可,维护成本极低
兼容C++11及更早版本的方案
如果项目不支持C++17特性,可以基于接口隔离原则拆分抽象基类,不要把所有方法都塞到同一个公共基类里:
- 先抽取公共方法基类,只放所有类共有的方法
- 针对不同类的独有方法,分别拆分独立的能力接口
- 调用独有方法前先做接口动态转换,转换成功说明当前实例支持该方法,再执行调用
代码示例:
// 公共方法基类,仅包含所有实现类共有的方法 class Colour { public: virtual void funcC() = 0; virtual ~Colour() = default; }; // Red独有能力接口,仅包含Red类特有的方法 class RedCapable { public: virtual void funcA() = 0; virtual ~RedCapable() = default; }; // Blue独有能力接口,仅包含Blue类特有的方法 class BlueCapable { public: virtual void funcB() = 0; virtual ~BlueCapable() = default; }; // 原有业务类继承对应的接口 class Red : public Colour, public RedCapable { public: void funcA() override { /* 原有业务逻辑 */ } void funcC() override { /* 原有业务逻辑 */ } }; class Blue : public Colour, public BlueCapable { public: void funcB() override { /* 原有业务逻辑 */ } void funcC() override { /* 原有业务逻辑 */ } }; // 统一接口类 class ColourInterface { private: Colour* obj = nullptr; public: void init(int mode) { delete obj; // 示例用裸指针简化逻辑,实际项目建议用std::unique_ptr管理内存避免泄漏 if (mode == 0) { obj = new Red(); } else { obj = new Blue(); } } ~ColourInterface() { delete obj; } void funcC() { obj->funcC(); } void funcA() { // 尝试转换为Red能力接口,转换成功说明当前实例支持该方法 if (auto* red_ptr = dynamic_cast<RedCapable*>(obj)) { red_ptr->funcA(); } } void funcB() { if (auto* blue_ptr = dynamic_cast<BlueCapable*>(obj)) { blue_ptr->funcB(); } } };
这种方案完全符合面向对象设计原则,每个抽象接口只负责单一能力,基类不会出现臃肿问题,后续新增方法只需要新增对应的能力接口即可,不需要修改原有基类的逻辑。
不推荐的方案
不要把所有独有方法都塞到公共基类里做空实现:
- 会导致基类职责混乱,后续维护的开发者无法区分哪些方法是公共能力、哪些是单个类的独有逻辑
- 新增实现类时很容易漏写需要的方法,空实现不会触发编译报错,问题会隐藏到运行时才暴露,排查成本极高
内容的提问来源于stack exchange,提问作者Pasin Kunamart
相关产品推荐
相关产品推荐

