软考知识点(更新)设计模式23种设计模式

23 种设计模式详解-CSDN博客

一、三大分类(理解设计模式的骨架)

设计模式根据目的(解决什么问题)分为三类:

  1. 创建型模式(5种):关注“对象怎么产生”。核心是将对象的创建和使用分离,避免用new硬编码。
  2. 结构型模式(7种):关注“类和对象怎么组合”。核心是通过灵活组合形成更大结构,同时保持结构的可扩展性。
  3. 行为型模式(11种):关注“对象之间怎么交互、怎么分配职责”。核心是管理算法、职责和通信的流程23 种设计模式的英文名称

二、逐个击破:用“一句话核心 + 一个生活比喻”理解每种模式

创建型模式(5种)- 解决“怎么造对象”

模式一句话核心生活比喻典型场景
单例一个类全局只有一个实例,提供全局访问点。一个国家只有一个总统。日志记录器、数据库连接池、配置管理器。
工厂方法父类定义创建对象的接口,子类决定实例化哪个类。不同品牌的汽车工厂,都生产汽车,但生产具体型号由分厂决定。一个框架需要支持多种产品,但不确定具体用哪种。
抽象工厂创建一系列相关或相互依赖的对象家族(不针对单个产品)。一家家具厂同时生产“现代风格”的椅子、桌子、沙发(整套)。需要切换整套UI皮肤(如Windows/Mac风格)
建造者(生成器模式)将一个复杂对象的构造与它的表示分离,同样的构建过程可以创建不同的表示。组装一台电脑:同样的步骤(装CPU、内存、硬盘),可选不同配件得到不同电脑。创建对象需要很多可选参数(如Builder模式在Java中的使用)。
原型通过复制(克隆)现有实例来创建新对象,而非通过new细胞分裂(一个细胞分裂成两个相同的细胞)。创建成本高(如数据库查询后的大对象),复制比新建更高效。

结构型模式(7种)- 解决“怎么组合类和对象”

模式一句话核心生活比喻典型场景
适配器让接口不兼容的类能够一起工作,像“转换插头”。使用电源转接头,让两脚插头插入三孔插座。集成第三方库,需要转换其接口为你的系统接口。
桥接将抽象部分与实现部分分离,使它们可以独立变化。不同颜色的形状:形状(圆形、方形)和颜色(红、蓝)可以任意组合,而不产生3×3=9个类。处理跨平台绘图(Windows、Linux)和不同图形(圆、方)。
组合将对象组合成树形结构以表示“部分-整体”的层次结构,使单个对象和组合对象的使用具有一致性。文件夹可以包含文件,也可以包含子文件夹。删除文件夹时,内部所有内容都被删除。树形菜单、文件系统、UI组件嵌套(一个面板包含多个按钮和子面板)。
装饰器动态地给一个对象添加一些额外的职责,比继承更灵活。给蛋糕加奶油、加水果、加巧克力,而不用改变蛋糕本身。给IO流添加缓冲功能(new BufferedInputStream(...))。
外观为子系统中的一组接口提供一个统一的、简化的高层接口。餐厅里的服务员:你只需要对服务员点菜,不用关心后厨如何协调。复杂库或框架的简化入口,例如编译一个程序只需要调用compile()方法。
享元运用共享技术高效支持大量细粒度对象,减少内存占用。文字处理器中的字符:每个字符(如字母'a')只存储一份实例,但位置信息不同。大量相似对象(如棋子、粒子效果、字符缓存)。
代理为另一个对象提供一个替身或占位符以控制对这个对象的访问。在需要比较通用和复杂的对象指针代替简单的指针时明星的经纪人:你需要联系明星谈合作,都得先经过经纪人。远程代理(RPC)、虚拟代理(延迟加载大图)、保护代理(权限控制)。

行为型模式(11种)- 解决“怎么分配职责和交互”

模式一句话核心生活比喻典型场景
策略定义一系列算法,封装每个算法,并使它们可以互换。出行策略:上班可以选择地铁、公交、打车,多种算法可互相替换。根据用户类型使用不同折扣计算、不同排序算法。
模板方法在父类中定义一个算法的骨架,将一些步骤延迟到子类中实现。制作饮料:烧水 → 冲泡(咖啡或茶)→ 倒杯 → 加调料。框架中定义不可变的流程,但部分步骤允许定制(如JUnit的setUp())。
观察者定义一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。订阅报纸:你订阅后,报社一有新报纸就送给你;取消订阅,就不再送。事件监听、响应式编程、发布-订阅系统。
迭代器提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部表示。电视机遥控器:你按“下一频道”,不用关心电视机内部怎么存频道。遍历集合(Java中的Iterator接口)。
责任链为请求创建一个处理者对象的链,每个处理者可以选择处理请求或传递给下一个。公司报销审批:员工 → 组长 → 经理 → 总监,根据金额决定谁能批。过滤器、中间件(如Web框架中的请求处理链)。
命令将请求封装为一个对象(命令),从而可以用不同的请求对客户进行参数化。餐厅的点菜单:服务员写下订单(命令),后厨根据订单做菜,订单可以排队、撤销。任务队列、撤销/重做功能、GUI按钮操作。
状态允许一个对象在其内部状态改变时改变它的行为,对象看起来像是修改了它的类。电梯的状态:开门、关门、运行、停止。不同状态下,按“关门”按钮的行为不同。工作流、状态机、订单状态(已支付、已发货、已完成)。
备忘录在不破坏封装的前提下,捕获并外部化一个对象的内部状态,以便之后恢复。游戏存档:保存当前游戏进度,以后可以载入存档回到那个状态。撤销(Ctrl+Z)、事务回滚、保存检查点。
中介者用一个中介对象来封装一系列对象的交互,使各对象不需要显式地相互引用。飞机塔台:飞机之间不直接通信,而是都通过塔台协调起降。表单组件互相关联(如选A时禁用B)、GUI控件协调。
访问者表示一个作用于某对象结构中的各元素的操作,使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。税局工作人员:去不同公司(元素)收税,针对不同公司执行不同计算规则,但公司本身不改变。对复杂对象结构(如AST抽象语法树)执行多种不同的操作。
解释器给定一个语言,定义它的文法的一种表示,并定义一个解释器,使用该表示来解释语言中的句子。计算器:输入“1+2*3”,解释器按规则解析并计算出结果7。正则表达式、SQL解析、数学表达式计算。

三、如何高效学习这23种模式?给你三步法

  1. 先记住分类和典型代表

    • 创建型:单例、工厂、建造者
    • 结构型:适配器、代理、装饰器
    • 行为型:策略、观察者、模板方法
    • 记住这9个,你已经能解决80%的常见设计问题。
  2. 对比容易混淆的模式(这是理解深度的关键):

    • 工厂方法 vs. 抽象工厂:一个是单个产品,一个是产品家族。
    • 适配器 vs. 桥接 vs. 装饰器:适配器是“改变接口”,桥接是“分离抽象与实现”,装饰器是“增强行为而不改变接口”。
    • 策略 vs. 状态:策略是“你主动选择算法”,状态是“状态自动改变行为”(二者结构相似但意图相反)。
    • 代理 vs. 装饰器:二者结构几乎一样,但意图不同:代理控制访问,装饰器增强功能。
  3. 在真实代码中寻找模式

    • 看Spring框架:单例(Bean默认)、工厂(BeanFactory)、代理(AOP)、适配器(HandlerAdapter)。
    • 看Java I/O:装饰器(new BufferedReader(new FileReader(...)))、适配器(InputStreamReader)。
    • 看GUI事件处理:观察者(Listener)、命令(Action)。

四、易混淆

1.切换主题

  • 策略模式:封装一组可互换的算法/行为(如不同的排序、加密、校验规则)。它只有一个变化维度,上下文通过组合持有一个策略接口,运行时替换策略对象。
  • 桥接模式:将抽象部分实现部分分离,让两者可以独立变化。它处理两个(或以上)独立变化维度,例如主题类型(黑夜/白天)和主题的具体渲染方式(Web/App/不同控件库)。

2.抽象工厂与桥接

抽象工厂的新例子:不同主题的游戏怪物生成工厂

假设你在开发一款RPG游戏,游戏中有几种怪物类型:战士法师弓箭手
游戏有两种主题(或者叫“场景/区域”):

  • 暗黑地牢主题:产生的怪物是暗黑战士、暗黑法师、暗黑弓箭手。
  • 森林精灵主题:产生的怪物是精灵战士、精灵法师、精灵弓箭手。

玩家进入不同的区域时,需要一次性切换到该主题对应的全套怪物(而不是混搭,例如不能让暗黑战士和精灵法师同时出现在同一个主题下)。 使用抽象工厂模式

怪物工厂接口(AbstractFactory):
    - 创建战士()
    - 创建法师()
    - 创建弓箭手()
暗黑地牢工厂(ConcreteFactory1):
    - 创建战士() -> 返回暗黑战士
    - 创建法师() -> 返回暗黑法师
    - 创建弓箭手() -> 返回暗黑弓箭手
森林精灵工厂(ConcreteFactory2):
    - 创建战士() -> 返回精灵战士
    - 创建法师() -> 返回精灵法师
    - 创建弓箭手() -> 返回精灵弓箭手

客户端代码只需要持有一个工厂接口,当切换主题时,替换整个工厂实例,后续所有怪物创建都自动属于新主题。

为什么这个例子不容易与桥接模式混淆?
  • 抽象工厂的核心:创建一系列相关产品(怪物家族),不同工厂产出不同产品族。产品之间是平行的、成套的,客户端会同时使用这一套产品。
  • 桥接模式的核心:将两个独立变化的维度分离(例如“怪物类型”和“攻击方式”可以独立扩展),让它们可以分别变化和组合。桥接强调的是组合而非创建,且不保证产品成套。

举例对比:
如果用桥接去设计怪物,可能会把“怪物种类(战士/法师)”和“攻击特效(火焰/冰霜)”分开,这样你可以自由组合出“火焰战士”、“冰霜法师”等,而不需要为每种组合单独创建工厂。这与抽象工厂的“成套使用”是完全不同的意图。

其他可选的抽象工厂例子(同样不易混淆)
  • 汽车生产线:SUV工厂生产(SUV轮胎、SUV发动机、SUV座椅);跑车工厂生产(跑车轮胎、跑车发动机、跑车座椅)。整车厂需要同一系列的零件,不能混装。
  • 餐厅套餐:儿童套餐工厂(汉堡、玩具、小杯可乐);成人套餐工厂(三明治、咖啡、沙拉)。顾客点餐时直接选择一种套餐,而不是自由组合。

3.桥接和组合

核心区别:意图与结构完全不同
维度桥接模式 (Bridge)组合模式 (Composite)
意图抽象实现分离,让两者能独立变化。表示部分-整体的层次结构,让客户统一处理单个对象和组合对象。
结构关系一个对象(抽象)持有另一个对象(实现)的引用。是一对一的组合,没有递归。容器对象持有多个子组件,子组件可以是叶子或另一个容器。是递归的树形结构
典型例子不同形状(圆、方)可以配合不同绘图API(Windows、Linux),两个维度独立扩展。文件夹可以包含文件,也可以包含子文件夹;一个菜单项可以包含子菜单。
用户视角用户知道抽象和实现是分离的,但使用时会组合起来。用户不区分叶子节点和容器节点,调用统一的方法(如 draw() 或 display())。
用一句话总结区别
  • 桥接模式:解决“类爆炸”问题。通过组合将两个独立变化的维度连接起来,避免为每种组合都写一个类。(强调维度解耦)
  • 组合模式:解决“树形结构”问题。通过递归组合让容器和叶子有相同的接口,让你可以像处理单个对象一样处理复杂树。(强调统一操作)
打个比方
  • 桥接:一辆汽车(抽象)有发动机(实现)。你可以换汽油发动机或电动发动机,而不改变汽车的整体设计。汽车和发动机是“有一个”的关系,且不递归。
  • 组合:一个军队。军队包含师,师包含团,团包含士兵。每个层级都支持“攻击()”操作。调用一个军队的攻击(),会递归调用所有下级单元的攻击()。这就是递归的树形结构。
为什么容易混淆“桥接”和“组合”?

因为两个模式都使用了对象组合(has-a)这个实现手段。但:

  • 桥接用的组合是“一个对象拥有另一个对象”,且层级固定(两层)。
  • 组合模式用的组合是“一个对象拥有多个对象”,且可以无限嵌套(树)。

记忆技巧

  • 桥接 = 横向组合(两个平级的维度拉在一起)
  • 组合 = 纵向组合(父子递归)

4.建造者和模板

核心区别一句话
  • 建造者模式步骤固定,但每步的具体实现可以完全不同,最终返回一个产品
  • 模板方法模式算法骨架固定,某些步骤由子类实现,通常不返回产品,而是执行一个流程
对比表
维度建造者模式 (Builder)模板方法模式 (Template Method)
类型创建型模式行为型模式
目的分步构建一个复杂对象,同样的构建过程可以产生不同的表示。定义一个算法的骨架,将一些步骤延迟到子类中实现,不改变算法结构。
结构Director(指导者)和Builder(建造者),通过组合实现。抽象父类定义模板方法和抽象/钩子方法,子类通过继承实现具体步骤。
变化方式更换Builder实现,改变产品的内部构成。继承父类并重写抽象方法,改变某些步骤的行为。
结果返回一个完整的Product对象。通常无返回值(或返回简单结果),重点是执行过程。
典型例子组装电脑(CPU、内存、硬盘顺序固定,但具体配件可变)。制作饮料(烧水、冲泡、倒杯、加调料,冲泡和加调料由子类决定)。
图示对照
建造者模式(简化类图)
Director ──持有──> Builder
    |                |
    |调用步骤         |实现步骤
    |                |
    +-- construct()  +-- buildPartA()
                     +-- buildPartB()
                     +-- getResult()

客户端:Director + ConcreteBuilder → 调用 getResult() 得到产品。

模板方法模式(简化类图)
AbstractClass
    + templateMethod() {   // 固定骨架
        step1();
        step2();
        step3();
    }
    + step1() { ... }      // 已有默认实现
    # step2() = 0;         // 抽象方法,子类实现
    # step3() = 0;         // 抽象方法,子类实现
}

客户端:子类继承并实现抽象方法,调用 templateMethod()

为什么容易混淆?

因为两者都封装了“不变的部分”(步骤顺序),并把“变化的部分”交给外部(建造者)或子类(模板方法)。但是:

  • 建造者的变化是通过替换对象(不同的Builder)实现的,是组合方式。
  • 模板方法的变化是通过继承(不同的子类)实现的,是继承方式。
一个更直观的对比例子

场景:制作披萨

  • 建造者模式:披萨的制作步骤固定(做面饼 → 加酱料 → 加主料 → 加芝士 → 烘烤)。你可以选择不同的PizzaBuilder:海鲜披萨建造者、素食披萨建造者。最终返回一个Pizza对象。同一个Director(厨师长)指挥每个建造者完成披萨。
  • 模板方法模式:定义一个PizzaMaker抽象类,其中makePizza()是模板方法,包含步骤:准备面饼、加配料、烘烤。但“加配料”是抽象方法,由子类(海鲜披萨Maker、素食披萨Maker)实现。不返回产品对象,而是直接执行制作流程(可能输出日志或保存到数据库)。
关键判别问题

如果你拿到一个设计,问自己:

  1. 是否要生成一个复杂对象,并且这个对象可以有不同的内部表示?
    → 是 → 建造者模式。
    → 否 → 可能是模板方法或其他。
  2. 是否通过继承来改变算法中的某些步骤,而整体流程保持不变?
    → 是 → 模板方法模式。
    → 否 → 可能是建造者。
  3. 是否有独立的“指导者”来控制步骤顺序,而建造者只负责具体实现?
    → 是 → 建造者模式。
  4. 是否客户端直接调用一个方法(模板方法),而无需关心对象构建?
    → 是 → 模板方法模式。

5.备忘录与命令

  • 备忘录模式(Memento)的核心是保存对象的内部状态,以便后续恢复(例如撤销到某个历史状态)。它只负责状态的“快照”和“恢复”,不记录动作本身
  • 题目需求是:记录用户的所有动作,并支持撤销与重做。这需要将每个操作(如滤镜、裁剪、调色)封装成对象,并维护一个操作历史列表,以便依次撤销或重做。这正是命令模式的典型应用场景:
    • 命令模式将请求(动作)封装为对象,可存储、排队、撤销(undo())、重做(redo())。
    • 备忘录可以作为命令模式内部保存状态的一种手段(例如在执行命令前保存对象状态,撤销时恢复),但单独使用备忘录无法实现动作的历史管理和重做。

6.桥接和状态

  1. 桥接模式需要“两个独立变化的维度”,但本题中的“照片特征”和“处理操作”并不是独立的——操作完全由特征决定,特征变化直接导致操作变化,不存在“自由组合”的场景。你不能说“低亮度特征”搭配“高对比度操作”,因为那是矛盾的。

  2. 桥接模式是一种静态结构,通常在系统初始化时组合好抽象与实现;而本题需求强调的是运行时根据状态动态选择行为,是动态的行为切换。

  3. 桥接模式不封装复杂的条件逻辑,它只是把两个维度连接起来;而本题明确提到“复杂的逻辑关系”,需要将这种逻辑集中管理,这正是状态模式的优势。

更适合的模式:状态模式(State)
  • 状态模式允许对象在内部状态改变时改变其行为,看起来就像修改了类。
  • 每个状态类封装了在该特征下应该执行的处理操作,以及状态转换逻辑。
  • 完美匹配“根据特征选操作”的需求。
Built with LogoFlowershow