23种设计模式精解:从入门到实战(三)封装—继承—多态

23种设计模式精解:从入门到实战(三)封装—继承—多态 面向对象三要素封装、继承、多态面向对象编程有三个核心特性通常称为三要素封装将数据和操作数据的方法绑定在一起对外隐藏实现细节继承子类复用父类的属性和行为实现代码复用与层次化设计多态同一接口在不同对象上有不同的实现运行时动态绑定三者的关系封装是基础继承是手段多态是目的。下面分别展开。封装为什么需要封装先看面向过程的写法。以牛郎织女的故事为例每个行为都需要通过参数明确指定执行者和对象qianNiu(HongNiang, XiaoHeBian); // 红娘将牛牵到小河边 zhuangChe(HongNiang, FangChe); // 红娘将纺车装车 searchNiu(NiuLang); // 牛郎寻找牛问题很明显函数内部需要处理各种人物和地点的判断行为主体不明确数据也没有保护——任何人都可以随意修改牛郎的牛。封装的实现封装的核心思想是将数据和操作数据的方法绑定在类中对外只暴露必要的接口。成员变量属性通常设为private保护内部状态成员函数行为设为public作为对外接口外部必须通过类提供的方法访问属性方法内部可以添加校验逻辑#include iostream #include string using namespace std; class NiuLang { private: string m_niu; // 牛私有保护 public: NiuLang(const string niu) : m_niu(niu) {} void searchNiu() { cout 牛郎寻找自己的牛 endl; } string getNiu() { // 通过公开方法访问私有属性 return m_niu; } }; int main() { NiuLang niuLang(黄牛); niuLang.searchNiu(); // 行为主体明确就是牛郎在找牛 string niu niuLang.getNiu(); return 0; }封装的优势数据安全牛郎的牛不能被外部随意修改必须通过类提供的方法访问调用简单调用者不需要关心内部实现只需要知道接口怎么用便于维护内部实现修改不影响外部调用只要接口保持不变继承继承的概念继承允许子类复用父类的属性和行为同时可以添加自己特有的功能。在 C 中子类可以继承父类的public和protected成员但private成员无法直接访问——注意私有成员仍然存在于子类对象的内存中只是从语法层面不可访问。一个直观的类比基因遗传相当于公共成员继承生来就有而知识技能相当于私有成员需要后天学习获得不能直接继承。继承的实现#include iostream using namespace std; class Person { public: void meeting() { cout 参加社交活动 endl; } void getMarried() { cout 结婚仪式 endl; } private: void secretMethod() {} // 子类无法访问 }; class NiuLang : public Person { // 公有继承 public: void searchNiu() { cout 寻找牛 endl; } // 子类特有方法 }; class ZhiNv : public Person { public: void spin() { cout 操作纺车 endl; } }; int main() { NiuLang nl; nl.meeting(); // 继承自 Person nl.getMarried(); // 继承自 Person nl.searchNiu(); // 自有方法 ZhiNv zn; zn.meeting(); // 同样继承自 Person zn.spin(); // 自有方法 return 0; }NiuLang和ZhiNv都继承了Person的meeting()和getMarried()不需要重复编写同时各自添加了特有的行为。继承的内存布局子类对象包含完整的父类子对象再加上子类新增的成员sizeof(Person); // 父类成员占用的空间 sizeof(NiuLang); // 父类空间 子类新增成员空间即使父类的私有成员在子类中不可访问它们仍然占据内存空间。这也是为什么sizeof子类通常大于sizeof父类。多态多态的三要素多态是面向对象最强大的特性实现它需要三个条件存在继承关系父类包含虚函数用virtual关键字声明子类重写父类虚函数用override标记多态的实现通过父类指针或引用指向子类对象调用虚函数时会执行子类的版本这就是动态绑定——运行时才确定调用哪个实现。#include iostream using namespace std; class Person { public: virtual void introduce() { // 虚函数 cout 我是普通人 endl; } }; class NiuLang : public Person { public: void introduce() override { // 重写override 关键字可校验签名 cout 我是牛郎 endl; } }; class ZhiNv : public Person { public: void introduce() override { cout 我是织女 endl; } }; void sayHello(Person* p) { // 父类指针可接收任意子类 p-introduce(); // 动态绑定调用实际对象的版本 } int main() { NiuLang nl; ZhiNv zn; sayHello(nl); // 输出我是牛郎 sayHello(zn); // 输出我是织女 return 0; }sayHello函数只依赖Person接口不需要知道传入的是哪个子类。新增子类时sayHello完全不需要修改——这就是多态带来的扩展性。纯虚函数与抽象类如果父类的虚函数没有默认实现可以声明为纯虚函数virtual 返回类型 函数名() 0; // 纯虚函数包含纯虚函数的类称为抽象类不能实例化对象派生类必须实现纯虚函数否则派生类也会成为抽象类抽象类的作用是定义接口规范强制子类实现特定行为。override 关键字override显式标记一个函数是重写父类虚函数编译器会检查函数签名是否匹配。如果签名写错比如参数类型不对编译器会直接报错避免因拼写错误导致多态失效。小结面向对象三要素各有分工封装解决数据安全和代码组织问题是面向对象的基础继承解决代码复用问题是实现多态的前提多态解决扩展性问题是面向对象的最终目的三者结合才能构建出可扩展、可维护、可读性好的大型软件系统。学习时建议按封装 → 继承 → 多态的顺序逐步深入每学一个就写一个小例子验证。