当前位置: 首页 > news >正文

C++类设计优化

不要通过继承添加行为!

1. 糟糕的实践:通过继承添加行为

假设有一个基础的 Bird 类:

cpp
#include <iostream>class Bird {
public:virtual void fly() {std::cout << "Bird is flying." << std::endl;}virtual ~Bird() = default; // 多态基类需要虚析构
};

现在,你想让一只鸟会唱歌,于是你继承了 Bird,并添加了一个 sing() 方法:

cpp
// 糟糕的设计:仅仅为了添加 sing() 而继承
class SingingBird : public Bird {
public:void sing() {std::cout << "SingingBird is singing." << std::endl;}
};

这样做会带来什么问题?

  1. 破坏了封装与里氏替换原则
    如果有一个函数期望接收 Bird 对象,传入 SingingBird 是合法的,但函数并不知道 SingingBird 有 sing() 方法,也无法调用它。这其实没有真正“扩展”功能,只是创建了一个新的类型。更重要的是,如果未来 Bird 的接口发生变化(例如增加了 void preenFeathers() 要求所有鸟在飞行前必须先梳理羽毛),而 SingingBird 没有重写该方法,程序可能出错。

  2. 导致类爆炸
    假如还需要会跳舞的鸟、会抓鱼的鸟、又会唱歌又会跳舞的鸟……那么就需要定义无数个子类:DancingBirdFishingBirdSingingDancingBird……类的数量会爆炸式增长,维护成本极高。

  3. 继承关系僵化
    通过继承添加的行为是静态的、编译期决定的,无法在运行时改变。一只 SingingBird 永远都会唱歌,你无法让一只普通的鸟临时获得唱歌能力。


2. 好的实践:使用接口(抽象基类)和组合

方案一:使用接口定义行为

将“唱歌”、“飞行”等行为抽象为纯虚接口(C++ 中的抽象类),然后让具体的鸟类实现这些接口。这样,行为与类型分离,一个类可以实现多个接口。

cpp
// 行为接口
class Flyable {
public:virtual void fly() = 0;virtual ~Flyable() = default;
};class Singable {
public:virtual void sing() = 0;virtual ~Singable() = default;
};// 基础鸟类(可以不实现任何行为,仅作为抽象基类)
class Bird {
public:virtual ~Bird() = default;// 可能有一些通用的属性,如 name、age 等
};// 具体的鸟类:只实现飞行
class FlyingBird : public Bird, public Flyable {
public:void fly() override {std::cout << "FlyingBird is flying." << std::endl;}
};// 具体的鸟类:只实现唱歌
class SingingBird : public Bird, public Singable {
public:void sing() override {std::cout << "SingingBird is singing." << std::endl;}
};// 具体的鸟类:同时实现飞行和唱歌
class SuperBird : public Bird, public Flyable, public Singable {
public:void fly() override {std::cout << "SuperBird is flying." << std::endl;}void sing() override {std::cout << "SuperBird is singing." << std::endl;}
};

优点:

  • 遵循里氏替换原则,任何需要 Flyable 的地方都可以传入 SuperBird 或 FlyingBird

  • 类数量大大减少,行为自由组合。

  • 运行时多态依然有效。

方案二:使用组合 + 策略模式

将行为封装成独立的类(策略),然后在 Bird 中通过成员变量持有这些行为的对象,从而动态地赋予鸟各种能力。

cpp
#include <iostream>
#include <memory> // for std::unique_ptr// 行为接口
class FlyBehavior {
public:virtual void fly() = 0;virtual ~FlyBehavior() = default;
};class SingBehavior {
public:virtual void sing() = 0;virtual ~SingBehavior() = default;
};// 具体的飞行行为
class SimpleFly : public FlyBehavior {
public:void fly() override {std::cout << "Flying simply." << std::endl;}
};class NoFly : public FlyBehavior {
public:void fly() override {std::cout << "Cannot fly." << std::endl;}
};// 具体的唱歌行为
class MelodiousSing : public SingBehavior {
public:void sing() override {std::cout << "Singing melodiously." << std::endl;}
};class NoSing : public SingBehavior {
public:void sing() override {std::cout << "Cannot sing." << std::endl;}
};// Bird 类:组合了飞行和唱歌行为
class Bird {
public:// 构造函数:注入具体的行为对象(通常通过依赖注入)Bird(std::unique_ptr<FlyBehavior> flyBehavior,std::unique_ptr<SingBehavior> singBehavior): flyBehavior_(std::move(flyBehavior)), singBehavior_(std::move(singBehavior)) {}// 执行飞行,委托给行为对象void performFly() {if (flyBehavior_) flyBehavior_->fly();}// 执行唱歌,委托给行为对象void performSing() {if (singBehavior_) singBehavior_->sing();}// 允许动态改变行为(setter 方法)void setFlyBehavior(std::unique_ptr<FlyBehavior> newBehavior) {flyBehavior_ = std::move(newBehavior);}void setSingBehavior(std::unique_ptr<SingBehavior> newBehavior) {singBehavior_ = std::move(newBehavior);}private:std::unique_ptr<FlyBehavior> flyBehavior_;std::unique_ptr<SingBehavior> singBehavior_;
};// 使用示例
int main() {// 创建一个既会飞又会唱歌的鸟Bird smartBird(std::make_unique<SimpleFly>(),std::make_unique<MelodiousSing>());smartBird.performFly();  // 输出: Flying simply.smartBird.performSing(); // 输出: Singing melodiously.// 动态改变行为:让这只鸟不会唱歌了smartBird.setSingBehavior(std::make_unique<NoSing>());smartBird.performSing(); // 输出: Cannot sing.return 0;
}

优点:

  • 高度灵活:行为可以在运行时动态改变(如上面的例子)。

  • 遵循开闭原则:增加新的行为(如 DanceBehavior)只需要添加新的策略类,无需修改 Bird 类。

  • 避免类爆炸:不需要为每种行为组合创建子类。

  • 符合单一职责原则:每个类只负责一件事(Bird 负责组合,具体行为类负责实现行为)。


总结

  • 通过继承添加行为(即仅仅为了添加新方法而创建子类)会导致代码僵化、违反里氏替换原则、引发类爆炸,是一种不灵活的面向对象设计。

  • 更好的做法是使用接口(抽象基类)或组合(策略模式)来赋予对象行为。这样代码更易于扩展、维护和测试,真正体现了“对扩展开放,对修改封闭”的开闭原则。

在 C++ 中,我们可以利用抽象类和模板来实现这些设计,但核心思想是:优先使用对象组合,而不是类继承。

http://www.jsqmd.com/news/495559/

相关文章:

  • python开发之路【第四章】:python程序流程控制
  • 零基础转行大模型,年薪 50W + 不是梦!保姆级学习路线 + 避坑指南,收藏这篇就够
  • AI分镜生成:Qwen-Image-Edit驱动的电影叙事革新
  • 2026六大城市高端腕表“闲置激活”终极档案:从无锡百达翡丽到南京积家,这些沉睡的资产正在悄悄贬值 - 时光修表匠
  • 借助 Windows CBT 挂钩实现自定义修改 MessageBox 阻塞式模态对话框中按钮的文字
  • 告别繁琐调试:用快马AI一键生成LaTeX高效排版模板与代码片段
  • 无锡芮生建设工程有限公司_一级资质防水修缮_14年专注房屋防水补漏 - 十大品牌榜单
  • AI 时代的程序员:从“建造者”到“定义者”
  • Suno音乐生成-从一个简单的提示开始,或者深入我们的专业编辑工具,您的下一首曲目就在一步之遥......
  • 2026年 铝液测氢仪厂家推荐排行榜,真空/在线/全自动/台式/移动型铝液测氢仪,铝合金氢含量检测仪专业品牌深度解析 - 品牌企业推荐师(官方)
  • 汽车洗美厂家常见问题解答(2026最新专家版) - 速递信息
  • 百心科技畅倍力复合果蔬后生元片常见问题解答(2026专家版) - 速递信息
  • 西恩士 国际认证清洁度解决方案 工业清洁度检测装置品牌典范 - 仪器权威论
  • 孝感二次消防设计机构全解析:2026年避坑指南与品牌推荐 - 2026年企业推荐榜
  • 如何打造专属音频控制中心?Seelen-UI媒体模块全攻略
  • 保姆级教程:用Qt Creator创建多工程联调开发环境(2024新版)
  • 电力能源行业:卓越性能保障电网稳定运行
  • 倍速链输送线源头厂家怎么选?搞电子和做电池的老铁们看过来
  • pid算法初学
  • 收藏!小白程序员必看:一文读懂AI大模型的核心能力与落地应用场景
  • 文档更新示例(路径:doc/README_zh.md)
  • 解决androidsutdio打开多个文件tab自动替换或者关闭问题
  • pg恢复
  • 虚幻引擎5编辑器界面全解析:从菜单栏到细节面板的保姆级指南
  • 用pyGAM搞定乳腺癌分类:从数据加载到模型调优全流程
  • 如何让技术图表绘制效率提升10倍?智能绘图工具赋能开发者与架构师
  • 2026年 温州宣传片拍摄制作推荐榜单:专业脚本策划、创意剪辑与视觉呈现一站式服务优选 - 品牌企业推荐师(官方)
  • Quartus开发中的关键文件格式解析与应用指南
  • NB-IoT物联网卡完整使用指南:从选卡到MQTT云端通信(2023最新版)
  • 实战指南:基于快马AI生成动态绑定文章数据的jiathis分享页