面向对象设计原则未完
面向对象设计原则(未完)
类和类之间的关系
关系类型 | 核心语义 | C++ 实现方式 | 生命周期依赖 | 典型示例 | |
---|---|---|---|---|---|
继承 | is-a(是一种) | class 子类 : 访问控制 父类 | 子类依赖父类 | 狗→动物 | |
实现 | is-a(符合规范) | 子类继承「纯虚函数抽象类」 | 类依赖接口 | 圆形→可绘制接口 | |
关联 | has-a(关联) | 成员变量存储对方指针 / 引用 + 容器 | 无 | 老师→学生 | |
聚合 | has-a(整体 - 部分) | 整体通过外部传入部分指针 | 无 | 学校→老师 | |
组合 | has-a(整体 - 部分) | 整体内部定义部分为「成员对象」 | 有(同生共死) | 汽车→发动机 | |
依赖 | use-a(依赖功能) | 调用静态方法 / 方法参数传入依赖对象 | 临时依赖 | 计算器→数学工具类 |
类和类之间的关系
1、继承()
基类会成为派生类的一部分。在语义层面:A is B。在类图的画法上:从派生类指向基类的空心三角箭头。
继承:先有基类,然后派生出新的类,也就是派生类。
泛化(一般化):先有派生类,将相同的属性抽出来,然后形成基类。
2、关联
2.1、双向的关联关系
双向的关联关系,彼此知道对方的存在,但是彼此不负责对方的生命周期。在语义层面:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:使用的是实心直线。
2.2、单向的关联关系
单向的关联关系,A知道对方的存在,但是B不知道A的存在。A不负责B的生命周期。在语义层面:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:从A指向B的实线箭头。
3、聚合
是一种稍强的关联关系,表现为整体与局部的关系,但是整体并不负责局部的销毁。在语义层面上:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:从局部指向整体的空心菱形箭头。
4、组合
是一种更强的关联关系,表现为整体与局部的关系,并且整体会负责局部的销毁。在语义层面上:A has B。在代码层面上:使用的是子对象。在类图的画法上:从局部指向整体的实心菱形箭头。
5、依赖
是两个类之间的一种不确定的关系,语义上是一种:A use B。这种关系是偶然的,临时的,并非固定的。在代码上表现为三种形式:
- B作为A的成员函数参数;
- B作为A的成员函数的局部变量或者B作为A的成员函数的返回值;
- A的成员函数调用B的静态方法。
类图的画法上:使用虚线的箭头从A指向B
6、总结
- 继承是垂直关系,其它的四种是水平关系;
- 从语义划分:继承 is;依赖:use;关联、聚合、组合:has;
- 从耦合强弱:依赖 < 关联 < 聚合 < 组合 < 继承;
- 从代码层面:依赖:主要体现在成员函数上; 关联、聚合、组合:主要体现在数据成员;继承:既有数据成员,也有成员函数;
面向对象的设计原则
原则名称 | 核心思想(通俗解释) | 设计目的(解决的问题) |
---|---|---|
单一职责原则(SRP) | 一个类 / 模块只干 “一件明确的事”,比如 “计算器只算数学题”“日志工具只记录操作”,不把无关功能堆在一起。 | 1. 降低复杂度:避免一个类又管计算又管日志,逻辑混乱; 2. 减少修改风险:改日志功能时,不会影响计算器的计算逻辑。 |
开放 - 封闭原则(OCP) | 对 “新增功能” 开放(能通过加新代码实现),对 “修改旧代码” 封闭(不用改已稳定的老代码)。比如 “图形面积计算”,新增 “三角形” 时,不用改原来 “圆形、矩形” 的计算逻辑,只加个三角形类。可扩展性 | 1**. 保护旧代码:避免改老代码时引入新 bug;** 2. 适应需求变化:新增功能时,不用推翻原有设计。 |
里氏替换原则(LSP) | 子类能 “完全代替父类” 干活,且不破坏父类的 “行为约定”。比如父类 “鸟” 约定 “会飞”,子类 “麻雀” 能飞(符合),但子类 “鸵鸟” 不会飞(违反)—— 因为用鸵鸟代替鸟时,“飞” 的功能就失效了。 可替代性 | 1. 保证继承的正确性:避免子类 “篡改” 父类逻辑,比如父类 “排序” 是正序,子类改成逆序,就会让调用者出错; 2. 确保代码兼容性:调用父类的地方,换子类也能正常运行。 |
依赖倒置原则(DIP) | 1. 高层模块(比如 “手机”)不依赖低层模块(比如 “安卓充电器、苹果充电器”),而是依赖 “抽象接口”(比如 “充电接口”);面向接口编程,依赖于抽象 2. 低层模块(充电器)反过来依赖抽象接口(符合充电接口的规则)。 | 1. 解耦:比如换充电器时,不用改手机的逻辑(只要新充电器符合充电接口); 2. 灵活替换:低层模块变了,高层模块不用动。 |
接口隔离原则(ISP) | 接口要 “小而专”,别搞 “大而全” 的 “万能接口”。比如 “工作者接口” 不该包含 “编程”“做饭”“开车” 所有方法,而是拆成 “程序员接口”(只有编程)、“厨师接口”(只有做饭),需要哪个就实现哪个。 | 1. 避免 “被迫实现无用方法”:比如让程序员实现 “做饭” 方法,明明用不上却必须写空逻辑; 2. 减少冗余:接口只包含必要的功能,逻辑更清晰。 |
迪米特法则(LOD) | 一个对象只和自己的 “直接朋友” 打交道,别管 “陌生人” 的事。“朋友” 指:自己的成员变量、方法参数、返回值;“陌生人” 指:朋友的朋友。比如 A 依赖 B,B 依赖 C,A 不该直接调用 C 的方法,要通过 B 中转。 | 1. 降低耦合:避免 A 和 C 直接关联,C 变了 A 不用改; 2. 简化依赖关系:对象只关注身边的朋友,不用管深层逻辑。 |
合成复用原则(CRP) | 优先用 “组合 / 聚合”(比如 “汽车有发动机”)代替 “继承”(比如 “汽车继承发动机”)来复用代码。比如想让汽车有 “发动” 功能,不用让汽车继承发动机(继承耦合太高),而是给汽车加个 “发动机” 成员(组合),调用发动机的发动方法。 | 1. 降低耦合:继承是 “强绑定”(改发动机要改汽车类),组合是 “弱绑定”(换发动机只需换成员变量); 2. 提高灵活性:能动态替换复用的功能(比如给汽车换涡轮增压发动机)。 |
三、设计模式
1、概念
设计模式是面向对象编程(OOP)中解决通用问题的成熟方案,核心目标是提升代码的复用性、可维护性、可扩展性,避免重复造轮子。它不是语法规则,而是基于大量实践总结的 “设计思想模板”,通常分为创建型、结构型、行为型三大类,每类对应不同场景的问题(如 “如何创建对象”“如何组织类结构”“如何交互通信”)。
设计模式的分类与核心目标
类别 | 核心目标 | 典型模式举例 |
---|---|---|
创建型 | 控制对象的创建过程,隐藏创建细节,降低耦合(如 “确保一个类只有一个实例”) | 单例模式、工厂方法模式、建造者模式 |
结构型 | 优化类或对象的组合关系,实现灵活的结构扩展(如 “适配不同接口”“动态增强功能”) | 适配器模式、装饰器模式、组合模式 |
行为型 | 定义对象间的交互逻辑,规范通信方式,减少依赖(如 “一个对象变了通知其他对象”) | 观察者模式、策略模式、迭代器模式 |
高频常用设计模式详解(C++ 实现)
1. 单例模式(Singleton):创建型
2. 工厂方法模式(Factory Method):创建型
定义
定义一个 “创建对象的接口”,但让子类决定具体创建哪个类的对象(如 “造手机”,父类定义造手机的流程,子类决定造苹果手机还是华为手机)。
核心思想
- 抽象工厂类:定义创建对象的纯虚函数(接口);
- 具体工厂类:继承抽象工厂,实现创建具体对象的逻辑;
- 产品类:抽象产品(定义产品接口)和具体产品(实现产品功能)。
C++ 实现(造手机示例)
#include <iostream>
#include <string>
using namespace std;
// ---------------------- 产品类 ----------------------
// 抽象产品:手机(定义产品接口)
class Phone {
public:
virtual ~Phone() = default;
virtual void showBrand() const = 0; // 纯虚函数:展示品牌
};
// 具体产品1:苹果手机
class IPhone : public Phone {
public:
void showBrand() const override {
cout << "产品:苹果iPhone 16" << endl;
}
};
// 具体产品2:华为手机
class HuaweiPhone : public Phone {
public:
void showBrand() const override {
cout << "产品:华为Mate 70" << endl;
}
};
// ---------------------- 工厂类 ----------------------
// 抽象工厂:手机工厂(定义创建产品的接口)
class PhoneFactory {
public:
virtual ~PhoneFactory() = default;
virtual unique_ptr<Phone> createPhone() const = 0; // 纯虚函数:造手机
};
// 具体工厂1:苹果工厂(造苹果手机)
class IPhoneFactory : public PhoneFactory {
public:
unique_ptr<Phone> createPhone() const override {
return make_unique<IPhone>(); // 创建具体产品
}
};
// 具体工厂2:华为工厂(造华为手机)
class HuaweiFactory : public PhoneFactory {
public:
unique_ptr<Phone> createPhone() const override {
return make_unique<HuaweiPhone>(); // 创建具体产品
}
};
// ---------------------- 测试 ----------------------
// 客户端代码:依赖抽象工厂和抽象产品,不依赖具体实现
void producePhone(const PhoneFactory& factory) {
unique_ptr<Phone> phone = factory.createPhone();
phone->showBrand();
}
int main() {
// 造苹果手机
IPhoneFactory iphoneFactory;
producePhone(iphoneFactory);
// 造华为手机(新增产品只需加工厂,无需改客户端)
HuaweiFactory huaweiFactory;
producePhone(huaweiFactory);
return 0;
}
适用场景
- 产品种类多变(如手机、电脑、家电,每种产品有多个品牌);
- 客户端不关心产品创建细节(只需要 “拿到产品用”,不用管 “怎么造”)。
优缺点
- ✅ 优点:新增产品只需加 “具体产品 + 具体工厂”,符合开闭原则;
- ❌ 缺点:产品种类过多时,工厂类数量会爆炸(如 10 种手机需要 10 个工厂)。