目录

面向对象设计原则未完

面向对象设计原则(未完)

类和类之间的关系

关系类型核心语义C++ 实现方式生命周期依赖典型示例
继承is-a(是一种)class 子类 : 访问控制 父类子类依赖父类狗→动物
实现is-a(符合规范)子类继承「纯虚函数抽象类」类依赖接口圆形→可绘制接口
关联has-a(关联)成员变量存储对方指针 / 引用 + 容器老师→学生
聚合has-a(整体 - 部分)整体通过外部传入部分指针学校→老师
组合has-a(整体 - 部分)整体内部定义部分为「成员对象」有(同生共死)汽车→发动机
依赖use-a(依赖功能)调用静态方法 / 方法参数传入依赖对象临时依赖计算器→数学工具类

类和类之间的关系

1、继承()

基类会成为派生类的一部分。在语义层面:A is B。在类图的画法上:从派生类指向基类的空心三角箭头。

https://i-blog.csdnimg.cn/direct/864aa5cef79643089259fa2f152095dd.png

继承:先有基类,然后派生出新的类,也就是派生类。

泛化(一般化):先有派生类,将相同的属性抽出来,然后形成基类。

2、关联
2.1、双向的关联关系

双向的关联关系,彼此知道对方的存在,但是彼此不负责对方的生命周期。在语义层面:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:使用的是实心直线。

https://i-blog.csdnimg.cn/direct/7b512374f5ff4df78fefd240f9e0a8b9.png

2.2、单向的关联关系

单向的关联关系,A知道对方的存在,但是B不知道A的存在。A不负责B的生命周期。在语义层面:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:从A指向B的实线箭头。

https://i-blog.csdnimg.cn/direct/73508e49239b40448cce6e6f05aac4f7.png

3、聚合

是一种稍强的关联关系,表现为整体与局部的关系,但是整体并不负责局部的销毁。在语义层面上:A has B。在代码层面上:使用的是指针或者引用。在类图的画法上:从局部指向整体的空心菱形箭头。

https://i-blog.csdnimg.cn/direct/cabc18c385554a02880a1fb2eb223465.png

4、组合

是一种更强的关联关系,表现为整体与局部的关系,并且整体会负责局部的销毁。在语义层面上:A has B。在代码层面上:使用的是子对象。在类图的画法上:从局部指向整体的实心菱形箭头。

https://i-blog.csdnimg.cn/direct/5fdeb17e74e848a2ac7ff28028263661.png

5、依赖

是两个类之间的一种不确定的关系,语义上是一种:A use B。这种关系是偶然的,临时的,并非固定的。在代码上表现为三种形式:

  • B作为A的成员函数参数;
  • B作为A的成员函数的局部变量或者B作为A的成员函数的返回值;
  • A的成员函数调用B的静态方法。

类图的画法上:使用虚线的箭头从A指向B

https://i-blog.csdnimg.cn/direct/8062874a2d0141709f146ae9e0fbbf7d.png

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 个工厂)。