设计模式的概念

1.核心问题:设计模式是什么?

  • 正式定义: 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案;
  • 通俗理解: 解决问题的固定套路
  • 使用原则: 慎用设计模式

2.设计模式怎么来的

1. 设计模式是怎么来的?

  • 满足设计原则后,然后慢慢迭代出来的。
  • 解读:
    • 设计模式不是凭空发明的,而是为了遵守设计原则(如 SOLID 原则:单一职责、开闭原则、依赖倒置等)而自然演化出来的结果。
    • 迭代这个词很重要。通常我们写代码是先写出“能跑”的代码,发现难以维护或扩展(违反了设计原则),然后进行重构,重构后的结构往往就符合了某种设计模式。
  • 结论: 先有原则,后有模式。模式是原则的最佳实践。

2. 设计模式解决了什么问题?

  • 核心目标:以小博大

    • 期望修改少量的代码,就可以适应需求的变化。
    • 解读:
      • 这是所有软件架构设计的终极梦想。
      • 在没有设计模式的“烂代码”中,改一个需求可能需要改动 10 个文件,甚至引发连锁反应(Bug)。
      • 而在设计良好的系统中,改一个需求可能只需要新增一个类,或者修改几行配置。这对应的就是设计原则中的“开闭原则” (Open/Closed Principle):对扩展开放,对修改关闭。

    2. 经典的“猫与房间”比喻

    比喻: 整洁的房间,好动猫,怎么保证房间的整洁?把猫关在一个笼子里。

    这个比喻可以这样对应到软件开发中:

    • 整洁的房间 = 整个软件系统(或者其中稳定的部分) 我们希望代码结构清晰、逻辑有序,不希望它被随意破坏。
    • 好动的猫 = 变化的需求(或者不稳定的逻辑) 需求就像这只猫,今天想往东,明天想往西,如果不加控制,它会在代码里“乱窜”,把原本整洁的“房间”(系统架构)搞得一团糟。
    • 笼子 = 设计模式(或者说抽象/接口/封装) 我们不能把猫(需求)扔掉,因为那是业务的核心。但我们可以给它做一个“笼子”(接口或抽象类)。
      • 不管猫在笼子里怎么跳(需求怎么变),只要它出不来(遵循接口契约),它就永远无法弄乱房间里的其他东西。

    总结

    设计模式做得最重要的一件事,就是找到代码里那只“好动的猫”,然后给它造一个合适的“笼子”。

    • 如果你的代码里没有“猫”(所有逻辑都很稳定),那你就不需要造“笼子”(不需要设计模式)。
    • 如果你满屋子都是“猫”(全是变化点),那你可能不需要笼子,而是需要把房子换成动物园(使用脚本语言或配置化系统)。

3.设计模式基础

1.面向对象的思想

1. 封装

  • 定义: 隐藏实现细节,实现模块化。
  • 解读:
    • 就像我们在上张图说的“笼子”。外部使用者不需要知道笼子怎么造的(内部数据结构),只需要知道怎么开门关门(Public 接口)。
    • C++ 视角: 使用 private / protected 隐藏成员变量,只暴露 public 方法。这是为了安全,防止外部随意修改对象内部状态导致崩溃。

2. 继承

  • 定义: 无需修改原有类的情况下,通过继承实现对功能的扩展。
  • 解读:
    • 这是代码复用的重要手段。这也直接对应了开闭原则(对扩展开放,对修改关闭)。
    • 注意: 在现代设计模式中,虽然继承很有用,但通常提倡 “多用组合,少用继承”。因为继承耦合度太高(父类变了,子类全得变)。

3. 多态

这是设计模式中最核心、最精彩的部分。图片将其分为了两类:

A. 静态多态

  • 图示例子: 函数重载 (Function Overloading)。
  • C++ 特性:
    • 编译期决定(Compile-time)。编译器在编译代码时,根据你传入参数的类型(是 int 还是 float,是一个参数还是两个参数),就已经决定了要调用哪个函数地址。
    • 扩展: 在 C++ 中,模板(Templates) 也是静态多态的一种重要形式(泛型编程)。

B. 动态多态

  • 图示例子: 继承中虚函数重写 (Virtual Function Overriding)。
  • C++ 特性:
    • 运行期决定(Runtime)。
    • 原理: 这是您感兴趣的底层知识——虚函数表 (vtable) 和 虚表指针 (vptr)
    • 父类指针指向子类对象时,调用 virtual 函数,程序会在运行时去查 vtable,找到真正应该调用的那个子类函数。
    • 价值: 这就是“隔离变化”的关键!我写代码时只用针对 Animal(父类)编程,运行时你可以给我传 Dog 也可以传 Cat,代码逻辑不用改就能执行不同的叫声。

4.补充知识

Gemini_Generated_Image_f6xh20f6xh20f6xh

1.早绑定
  • 别名: 静态绑定。
  • 发生时间: 编译期 (Compile-time)
  • 机制:
    • 编译器在编译代码时,已经确切知道 func() 这行代码对应的是内存中的哪个函数地址。
    • 编译器直接生成一条 CALL 指令跳转到那个固定的地址。
2. 晚绑定
  • 别名: 动态绑定。
  • 发生时间: 运行期 (Runtime)
  • 机制:
    • 编译器在编译时不知道具体要调哪个函数(因为指针可能指向父类,也可能指向子类)。
    • 程序运行时,会去查看对象的实际类型(通过虚函数表 vtable),然后找到正确的函数地址进行跳转。

总结与联系

如果您把这几张图串起来看,逻辑链条是非常清晰的:

  1. OOP 三大特性(本图): 提供了技术实现的手段(封装、继承、多态)。
  2. 设计原则(开闭原则等): 指导我们如何正确使用这些手段。
  3. 设计模式(第一张图): 是前人总结出来的、符合原则的、解决特定问题的“最佳实践套路”。

2.设计原则

1. 单一职责原则

  • 一句话: 一个类只负责一件事。
  • 解读: 如果一个类承担了太多的职责(例如:既负责连接数据库,又负责计算业务数据,还负责生成 HTML 报表),那么它就会变得极其脆弱。任何一个职责的变化都可能导致这个类出 Bug。
  • 目标: 高内聚,低耦合。

2. 开闭原则

  • 一句话: 对扩展开放,对修改关闭。
  • 解读: 这就是你之前那个“猫和笼子”比喻的精髓。
    • 当需求变化时,我们应该通过增加新的代码(扩展子类、实现接口)来适应,而不是去修改原本已经运行良好的源代码
  • 关联: 这是所有设计模式的终极目标。

3. 里氏替换原则

  • 一句话: 子类必须能够替换掉它们的父类。
  • 解读: 继承的规范。子类不能破坏父类设定的“契约”。
    • 反例: 父类是 ,有一个 飞() 的方法。你写了一个子类 鸵鸟 继承 ,但是鸵鸟不会飞,调用 飞() 会报错。这就违反了 LSP。
    • 后果: 如果违反 LSP,多态(晚绑定)就会出问题,因为父类指针指到子类时,行为不可预测。

4. 接口隔离原则 一句话: 接口要小而专,不要大而全。

  • 解读: 客户端不应该依赖它不需要的接口。
    • 例子: 不要搞一个庞大的 IWorker 接口,里面既有 coding() 又有 cleaning()。因为程序员不需要 cleaning(),保洁阿姨不需要 coding()。应该拆分成 ICoderICleaner 两个接口。

5. 依赖倒置原则

  • 一句话: 面向接口编程,不要面向实现编程。
  • 具体含义:
    • 高层模块(业务逻辑)不应该依赖低层模块(底层细节),两者都应该依赖其抽象(接口)。
    • 抽象不应该依赖细节,细节应该依赖抽象。
  • 关联: 这就是晚绑定的应用场景。你依赖的是抽象的 Animal(接口),而不是具体的 Dog(细节)。

另外两个重要原则

6. 迪米特法则 (LoD - Law of Demeter) / 最少知道原则

  • 一句话: 只与你的直接朋友交谈,不跟“陌生人”说话。
  • 解读: 一个对象应该对其他对象有最少的了解。
    • 例子: 如果你想让朋友帮你拿个快递,你直接告诉朋友“帮我拿快递”。你不应该:从朋友口袋里掏出钱包,拿出身份证,自己去驿站拿。
    • 作用: 降低类与类之间的耦合度。

7. 合成复用原则 (CRP - Composite Reuse Principle)

  • 一句话: 多用组合(Has-A),少用继承(Is-A)。
  • 解读:
    • 继承的耦合度太高了(白箱复用),父类变了子类必变。
    • 组合的耦合度低(黑箱复用),只要接口不变,具体的实现类随便换。
    • 例子: 想要给一个类增加功能,尽量把另一个类作为成员变量传进来(组合),而不是去继承它。

4.如何学习设计模式

1.明确目的

1.在现有设计模式的基础上,扩展代码

  • 这对应了 “开闭原则”
  • 当你接手一份代码(或者看开源库源码)时,如果能认出它用了什么模式,你就知道应该在哪里写新代码,在哪里绝对不能动。

2。做功能抽象时,如何选择设计模式

  • 这是从“读代码”进阶到“写架构”。
  • 面对一个复杂的业务需求,你能迅速判断出:这里应该用“策略模式”来消除 if-else,还是用“观察者模式”来做解耦?

2.学习步骤

.该设计模式解决了什么问题? (核心心法)

  • 关键点: 稳定点 vs 变化点
  • 解读:
    • 每当你学习一个新模式(比如工厂模式),首先要问自己:“在这个模式里,哪部分是那只乱动的‘猫’(变化点)?哪部分是那个整洁的‘房间’(稳定点)?”
    • 如果找不到变化点,你就无法理解这个模式存在的意义。

2. 该设计模式的代码结构是什么? (招式)

  • 解读:
    • 这是最基础的类图(UML)和代码模板。
    • 对于 C++ 开发者,这意味着要搞清楚:基类是谁?虚函数在哪里?指针怎么传?谁持有谁的引用?

3. 符合哪些设计原则? (内功)

  • 解读:
    • 验证这个模式是否合理。
    • 比如:模板方法模式 (Template Method) 主要是为了复用算法骨架(代码复用),策略模式 (Strategy) 主要是为了遵循开闭原则(替换算法)。

4. 如何在上面扩展代码? (实战演练)

  • 解读:
    • 光看不练假把式。
    • 给自己出一个考题:假设现在需求变了,需要增加一种新的情况,我应该怎么写代码?
    • 标准: 如果你发现扩展新功能只需要“新增一个类”,而不需要修改原来的核心逻辑,恭喜你,你学会了。

5. 该设计模式有哪些典型应用场景? (举一反三)

  • 联系工作场景:
    • 你之前的项目中,有没有哪个模块改起来特别痛苦?如果用了这个模式,会不会好一点?
  • 开源框架: (特别适合您的 C++ 提升路径)
    • 不要只看 Demo,要去看看大神们是怎么用的。
    • 比如:
      • Reactor 模式: 去看 libevent, muduo, 或者 nginx 的核心循环。
      • 单例模式: 看看 STL 或者某些 Log 库是怎么初始化全局对象的。
      • 工厂模式/构建者模式: 看看 Protobuf 是怎么生成 Message 对象的。
posted @ 2025-12-20 20:40  belief73  阅读(2)  评论(0)    收藏  举报