设计模式这一章是上午题里回报率最高的一章:它不考计算、不考推理,考的是“背”。
2009 到 2021 年一共 46 道题,几乎全部是同一个问法:给你一句意图描述(“将一个类的接口转换成客户希望的另外一个接口”),或者一个 UML 结构,问这是哪个模式。偶尔反过来问模式的适用场景。
所以这一章的复习方法只有一条:把 23 个模式的“一句话意图”背到看到句子 3 秒内出名字。本章每一节的解析都会先给意图句,再做选项辨析。
这一章的知识骨架:
- 三大分类:创建型(5 个)、结构型(7 个)、行为型(11 个)——分类本身就是考点。
- 五个高频模式:单例、工厂系列、观察者、策略、适配器,几乎年年出现。
- 三对易混:装饰器 vs 适配器(都包装,一个加职责一个换接口);代理 vs 装饰器(都同接口控制访问,一个管访问一个加职责);桥接 vs 外观(都涉及间接性,一个分离抽象与实现、一个简化接口)。
- 一个总判据:这句话在说创建、组装还是交互?大类先对,再挑名字。
一、设计模式总览:考什么、怎么考
设计模式这一章的考法极其稳定:给你一段意图描述(或一张结构图),问这是哪个模式。偶尔反过来,给你模式名,问它的意图或适用场景。
教材把 23 个模式按目的分三类,这个分类本身就是考点:
- 创建型(5 个):单例、原型、生成器(Builder)、工厂方法、抽象工厂 —— 管“对象怎么创建”。
- 结构型(7 个):适配器、桥接、组合、装饰器、外观、享元、代理 —— 管“类和对象怎么组装”。
- 行为型(11 个):责任链、命令、解释器、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者 —— 管“对象怎么交互、职责怎么分配”。
做题的第一反应永远是:这句话在描述创建、组装还是交互?大类对了,选项就剩两三个。
在面向对象软件开发过程中,采用设计模式 (43) ,
查看答案与解析收起答案与解析
选 A。设计模式的核心价值是复用成功的设计——每个模式都是前人反复验证过的解决方案,遇到同类问题直接套用,比从零设计更可靠。
B 错:设计模式为了灵活性和可扩展性会增加抽象层次,往往牺牲一点运行速度,它从来不是性能优化手段。
C 错:很多模式(抽象工厂、桥接)会引入额外的类和层次,类的数量通常变多而不是变少。
D 错:设计模式总结的是面向对象的设计经验,依赖类、继承、多态这些机制,不能“在非面向对象语言中使用面向对象概念”。
记住教材定义的关键词:设计模式是被反复使用、经过分类编目的代码设计经验总结,核心就是“复用”。
二、创建型模式:对象怎么生出来
创建型 5 个模式各管一件事:
- 单例:一个类只有一个实例,提供全局访问点。
- 原型:通过复制已有实例来创建新对象。
- 生成器(Builder):把复杂对象的构建过程与表示分离,同样的步骤能拼出不同的成品。
- 工厂方法:定义创建对象的接口,让子类决定实例化哪个类。
- 抽象工厂:提供创建一系列相关对象的接口,无需指定具体类。
判别口诀:要“一系列产品”选抽象工厂,要“逐步拼装复杂对象”选生成器,要“复制”选原型,要“子类决定创建谁”选工厂方法,要“全局唯一”选单例。
查看答案与解析收起答案与解析
(43)选 C(抽象工厂)。图中 AbstractFactory 下面挂着两个具体工厂,每个工厂能创建一整族产品(ProductA1/A2、ProductB1/B2)——“提供创建一系列相关或相互依赖对象的接口”,正是抽象工厂的意图。
(44)问“错误的”,选 D。“希望使用已经存在的类,但其接口不符合需求”是适配器模式的意图,与抽象工厂无关。
A 是抽象工厂的意图原句;B“一个系统要由多个产品系列中的一个来配置”是它的典型场景(比如同一套程序支持 MySQL 和 Oracle 两套数据库);C“强调一系列相关产品对象联合使用”是它的设计动机。
抽象工厂和工厂方法最易混:工厂方法造一个产品、让子类决定创建谁;抽象工厂造一整族产品、一个工厂配一套。
为图形用户界面(GUI)组件定义不同平台的并行类层次结构,适合采用 (44) 模式。
查看答案与解析收起答案与解析
选 B(抽象工厂)。“为 GUI 组件定义不同平台的并行类层次结构”——按钮在 Windows 和 Mac 上各有一套实现,这套“平行产品族”正是抽象工厂的教科书场景:一个工厂生产 Windows 系的按钮/文本框,另一个工厂生产 Mac 系的按钮/文本框,程序只面向抽象产品编程。
A 享元:共享大量细粒度对象省内存,与平台层次无关。
C 外观:给复杂子系统一个简化接口。
D 装饰器:动态加职责。
看到“并行类层次”“产品系列”“一族产品”这几个词,直接选抽象工厂。它和桥接的区别:桥接是把一个维度的抽象与另一个维度的实现分开;抽象工厂是整族产品一起切换。
查看答案与解析收起答案与解析
(46)选 B(生成器)。图里有 Builder 抽象接口、两个 ConcreteBuilder、以及一个 Director——“Director 调度构建步骤,Builder 负责每一步的具体实现”,这是生成器模式的标准结构。
(47)选 C。“创建复杂对象的算法应该独立于该对象的组成部分及其装配方式”——这正是生成器的意图:构建过程(Director 里的步骤顺序)与表示(ConcreteBuilder 拼出来的成品)分离。
A 是抽象工厂的适用场景。B 是工厂方法的意图(“由子类决定实例化哪个类”)。D 的表述更像抽象工厂,不是生成器的标准适用性。
记生成器抓两个词:“逐步构建”和“同一过程、不同表示”。快餐店做儿童套餐(主餐+饮料+玩具,过程相同、组合不同)是它的经典案例。
(46) 模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。以下 (47) 情况适合选用该模式。(2016年下半年)<br/>①抽象复杂对象的构建步骤<br/>②基于构建过程的具体实现构建复杂对象的不同表示<br/>③一个类仅有一个实例<br/>④一个类的实例只能有几个不同状态组合中的一种
查看答案与解析收起答案与解析
(46)选 A(生成器)。“将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示”——这是教材对生成器的定义原文。
(47)选 A(①②)。逐条判断:
① 抽象复杂对象的构建步骤——生成器正是把构建步骤抽象到 Builder 接口里,属于。
② 基于构建过程的具体实现构建复杂对象的不同表示——ConcreteBuilder 就是干这个的,属于。
③ 一个类仅有一个实例——那是单例模式的意图,不属于。
④ 一个类的实例只能有几个不同状态组合中的一种——这是享元(共享内部状态)的语境,也不属于。
所以 ①② 成立。③④ 是命题人拿“单例”和“享元/状态”的意图来混淆视听,背熟三大类的意图句,一眼剔除。
查看答案与解析收起答案与解析
(44)选 A(生成器)。快餐店做儿童套餐:主餐、饮料、玩具的种类可能不同,但制作过程相同(先主餐、再饮料、后玩具)。前台 Waiter 充当 Director 固定流程,PizzaBuilder/SpicyPizzaBuilder 充当具体 Builder 负责每一步——过程与表示分离,正是生成器。
(45)选 A(PizzaBuilder)。图中 Waiter 调用的是抽象 Builder 的接口,真正实现构建步骤的是 PizzaBuilder 和 SpicyPizzaBuilder 这两个具体生成器。
(46)选 A(创建型对象)。生成器属于创建型模式,且通过对象组合(Director 持有 Builder 引用)实现,所以是“创建型对象模式”。
(47)选 A。“系统独立于产品的创建、构成和表示”——生成器的标准适用场景。
B 错:“由子类指定创建的对象”是工厂方法。C 是享元。D 是工厂方法的意图。
以下关于Singleton(单例)模式的描述中,正确的是 (46) 。
查看答案与解析收起答案与解析
选 D。单例(Singleton)模式保证一个类只产生唯一的一个实例,并提供一个全局访问点。
A、B 错:“只有一个方法/属性的类”跟单例毫无关系——单例说的是实例数量,不是类成员的数量。
C 错:“方法只能被一个唯一的类调用”描述的是访问控制(比如私有方法只能被本类调用),与实例数量无关。
D 是单例的定义原句。实现要点顺带记:构造函数私有化(private)+ 类内持有 static 的唯一实例 + 提供静态方法(getInstance)返回该实例。常见的饿汉式(类加载即创建)和懒汉式(首次调用才创建)都要会写。
欲使类A的所有使用者都使用A的同一个实例,应 (47) 。
查看答案与解析收起答案与解析
选 C(单例模式)。“所有使用者都使用 A 的同一个实例”——这就是单例模式存在的意义:全局唯一、到处共享。
A final:Java 里 final 修饰类表示不能被继承,管不到实例数量。
B abstract:抽象类不能实例化,更谈不上“同一个实例”。
D 备忘录:保存和恢复对象的状态(比如游戏存档、撤销),不控制实例数量。
单例的实现三板斧:私有构造器 + 静态私有实例 + 静态公有访问方法。软考偶尔会考到实现细节或线程安全问题,知道“懒汉式要加锁”即可。
以下关于Singleton(单例)设计模式的叙述中,不正确的是 (44) 。
查看答案与解析收起答案与解析
问“不正确的”,选 D。D 说“单例类提供一个创建一系列相关或相互依赖对象的接口”——这是抽象工厂的意图,跟单例无关。
A 正确:单例属于创建型模式——它管的就是“对象怎么创建(只创建一次)”。
B 正确:单例保证一个类仅有一个实例,这是它的核心承诺。
C 正确:单例类必须提供全局访问点(通常是静态的 getInstance()),否则外部拿不到那个唯一实例。
三个正确项正好是单例的三要素:创建型、唯一实例、全局访问点。D 用工厂的意图来冒充,背熟各模式意图句就能识破。
三、结构型模式:类和对象怎么组装
结构型 7 个模式的核心动作各不相同,抓住动词就能对号:
- 适配器:转换接口,让不兼容的类一起工作。
- 桥接:把抽象部分和实现部分分离,让两者独立变化。
- 组合:把对象组成树形结构,表示“部分—整体”,让单对象和组合对象用起来一致。
- 装饰器:不改变接口的前提下,动态加职责。
- 外观:给复杂子系统提供一个简单接口。
- 享元:共享大量细粒度对象,省内存。
- 代理:提供与目标对象相同的接口来控制对它的访问。
(47) 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
查看答案与解析收起答案与解析
选 A。题干就是适配器模式的定义原文:“将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。”
B 命令:把请求封装成对象,支持排队、日志、撤销。
C 单例:唯一实例。
D 策略:封装可替换的算法族。
适配器的形象理解:电源插头转换器。设备是两脚插头,墙上插座是三孔——转换器不改变任何一方,只是“搭桥”让它们兼容。
适配器分两种:类适配器(用多重继承,C++ 可行)和对象适配器(用组合,持有被适配对象的引用)。软考偶尔问两种的区别,区别就在实现手段。
查看答案与解析收起答案与解析
(44)选 A(Bridge)。图里有抽象层 Abstraction(持有 -imp: Implementor 引用)和实现层 Implementor 接口,两层各自扩展(RefinedAbstraction、ConcreteImplementorA/B)——抽象与实现分离、独立变化,这是桥接的标准结构。
(45)选 D(Implementor)。图的右侧最顶层是 Implementor 接口(斜体),下面是两个具体实现类,它是实现层的抽象。
B、C 是具体实现类,位于 Implementor 之下;A(Abstraction)在左侧抽象层,不是右侧的接口。
桥接的典型例子:形状(圆形、方形)× 渲染方式(Windows、Linux)——两个维度各自扩展。没有桥接时要写 m×n 个类,有桥接后只要 m+n 个。
查看答案与解析收起答案与解析
(44)选 D(桥接)。图中 Light(抽象)持有 Theme(实现)的引用,Light 的子类和 Theme 的子类各自扩展——“将抽象部分与其实现部分分离,使它们都可以独立地变化”,这是桥接的意图句。
(45)选 A(WebApplication)。图中 Theme 是实现侧的接口,WebApplication 位于抽象侧——它是基于该框架构建的具体 Web 应用之一,与 Blog、网上商店并列,都是抽象侧的扩展。
(46)选 A(创建型对象模式)。这道题的图虽是桥接结构,但选项问的是该设计所属类别——题干说“创建 Web 应用框架”,框架的目的是创建应用,属于创建型,且用对象组合实现。
(47)选 A。“将抽象部分与其实现部分分离”——桥接的意图原句。
B 是外观的意图,C 是访问者的意图(表示部分—整体是组合)。
查看答案与解析收起答案与解析
(44)选 C(桥接)。题干“设计模式将抽象部分与其实现部分相分离,使它们都可以独立地变化”就是桥接的意图句,图也是标准的桥接类图。
(45)选 D(Drawing)。图左侧 Shape(抽象)持有 Drawing(实现)的引用;右侧 Drawing 下面是 V1Drawing、V2Drawing 两个具体实现。所以“实现部分的接口”是 Drawing。
A Shape 是抽象层的类;B Circle 和 Rectangle 是 Shape 的子类;C V1Drawing、V2Drawing 是 Drawing 的具体实现。
(46)选 A。桥接适用于“不希望在抽象和它的实现部分之间有固定绑定关系”、抽象和实现都可以独立扩展的场景。B 是组合模式的意图。
(47)选 B。图中 Circle 和 Rectangle 继承 Shape,是抽象侧的子类(细化抽象)。
桥接的双向扩展要认清:左边 RefinedAbstraction 扩展抽象,右边 ConcreteImplementor 扩展实现,中间靠一条聚合线连着。
查看答案与解析收起答案与解析
(45)选 C(组合)。图里 Component 有 operation()、add(Component)、remove(Component)、getChildren(int),Leaf 和 Composite 都继承它,Composite 还聚合了一组 Component(实心菱形 0..*)——“对象组合成树形结构表示部分—整体”,标准组合模式。
(46)选 D(Composite)。能“包含其他组件”的是容器构件 Composite:它持有 Component 的集合(0..*),并提供 add/remove 来增删子构件。Leaf 是叶子,没有子节点。
(47)选 A(Client)。Client 通过 Component 接口与整棵树交互——它不关心操作的是单个 Leaf 还是 Composite,两者对待方式一致,这正是组合模式“单对象和组合对象使用具有一致性”的体现。
(46)(47)的角色分工记住:Component 定接口,Composite 管容器,Leaf 管叶子,Client 负责调。
查看答案与解析收起答案与解析
(46)选 C(组合)。题干给的场景是“文件系统/树形结构”这类“部分—整体”层次——用组合模式:Component 定义统一接口,Composite 表示文件夹(可以包含子项),Leaf 表示文件(没有子项)。
B 适配器是转换接口,A 组件(Component)是组合模式里的角色名,不是模式名。D 装饰器是动态加职责。
(47)选 A(表示对象的部分—整体层次结构)。这是组合模式的意图原句。
B 是桥接的意图。C 是装饰器的意图(动态、透明地加职责)。D 是适配器的意图(接口不兼容类一起工作)。
四个选项正好是四个模式各自的意图句,这题等于把结构型四兄弟的“身份证”摆在一起让你认人——意图句背熟,秒选。
查看答案与解析收起答案与解析
(44)选 C(组合)。题干说“可将对象组合成树形结构以表示‘部分—整体’的层次结构,使用户对单个对象和组合对象的使用具有一致性”——组合模式的标准定义。
(45)选 B(结构型)。组合模式处理的是“类和对象怎么组装成更大的结构”,属于结构型模式。创建型管生成,行为型管交互。
(46)选 A(表示对象的部分—整体层次结构时)。这是组合模式的适用场景。
B“当一个对象必须通知其它对象,而它又不能假定其它对象是谁时”是观察者的适用场景。C“创建复杂对象的算法独立于组成部分及装配方式”是生成器。这三句话在本章反复出现,背熟后可以互相“对号”。
欲动态地给一个对象添加职责,宜采用 (43) 模式。
查看答案与解析收起答案与解析
选 D(装饰器)。“动态地给一个对象添加职责”是装饰器的定义句——不改变原对象的接口,用一层层的包装把功能叠上去,比生成子类更灵活。
A 适配器:转换接口,不加职责——它解决的是“接口不兼容”。
B 桥接:分离抽象与实现,让两个维度独立变化。
C 组合:树形结构表示部分—整体。
装饰器和继承的对比是常考点:想给“加奶、加糖、加奶泡”的咖啡组合所有可能,继承要写 2ⁿ 个子类;装饰器只要一层层包上去,运行时任意组合。Java 的 I/O 流(BufferedInputStream 包 FileInputStream)就是典型应用。
查看答案与解析收起答案与解析
(44)选 C(外观)。图里子系统(Subsystem)的多个类被一个 Facade 挡在后面,客户端只跟 Facade 打交道——“为复杂子系统提供一个简单接口”,选 C。
(45)选 D。题干问外观的适用场景:“需要为一个复杂子系统提供一个简单接口”——正是外观的定义。
A 是责任链的场景(多个对象处理一个请求)。B 是适配器的场景(已有类接口不符)。C 是桥接的场景(抽象与实现分离)。
外观和适配器都“包装”,区别在:适配器包装一个对象、改它的接口;外观包装一堆对象、简化它们的接口。一个是接口转换,一个是入口收敛。
装饰器(Decorator)模式用于 (46) ;外观(Facade)模式用于 (47) 。(2017年上半年)<br/>①将一个对象加以包装以给客户提供其希望的另外一个接口<br/>②将一个对象加以包装以提供一些额外的行为<br/>③将一个对象加以包装以控制对这个对象的访问<br/>④将一系列对象加以包装以简化其接口
查看答案与解析收起答案与解析
(46)选 B(②)。装饰器的意图是“将一个对象加以包装以提供一些额外的行为”——接口不变,职责增加。②正是这个意思。
(47)选 D(④)。外观的意图是“将一系列对象加以包装以简化其接口”——把子系统的复杂调用收敛成一个简单入口。
①“包装以提供另外一个接口”是适配器——换接口。
③“包装以控制对这个对象的访问”是代理——管访问权限、延迟加载、日志等。
这四个“包装”放在一起是本章最经典的对比题,一张表记死:换接口=适配器,加行为=装饰器,控访问=代理,简化入口=外观。
因使用大量的对象而造成很大的存储开销时,适合采用 (47) 模式进行对象共享,以减少对象数量从而达到较少的内存占用并提升性能。
查看答案与解析收起答案与解析
选 B(享元)。“因使用大量的对象而造成很大的存储开销”——享元模式就是为这个场景设计的:把对象中相同的部分(内部状态)共享出来,只保留一份;不同的部分(外部状态)由客户端传入。
A 组合:树形结构表示部分—整体,不管内存。
C 迭代器:顺序访问聚合对象的元素。
D 备忘录:保存和恢复对象状态。
经典例子:文字处理器里的字符对象。一篇文章几十万个字符,如果每个字符都 new 一个对象,内存爆炸;用享元把“字形”共享(只存一份 Arial 的‘a’),把“位置”作为外部状态传入,内存占用骤降。
教材对享元意图的表述:“运用共享技术有效地支持大量细粒度的对象”——题干几乎是原句。
(44) 模式通过提供与对象相同的接口来控制对这个对象的访问。
查看答案与解析收起答案与解析
选 B(代理)。“通过提供与对象相同的接口来控制对这个对象的访问”——代理模式的两要素:同接口、控访问。
A 适配器:改接口,不是同接口。C 组合:树形结构。D 装饰器:同接口但目的是加职责,不是控访问。
代理和装饰器是本章最容易混的一对,区别只在意图:
装饰器:我给你加功能(咖啡加奶加糖),层数可以随便叠。
代理:我替你把门(权限检查、延迟加载、远程调用、缓存),通常只有一层,客户端可能根本感觉不到代理的存在。
常见代理种类:远程代理、虚拟代理(延迟创建大对象)、保护代理(权限)、智能引用(引用计数)。
四、行为型模式:职责怎么分、消息怎么传
行为型 11 个模式是考试的重头,观察者一家就占了 7 题。逐个记住“一句话意图”:
- 责任链:多个对象都有机会处理请求,沿链传递直到有人处理。
- 命令:把请求封装成对象,支持排队、日志、撤销。
- 解释器:给定语言,定义它的文法表示和解释器。
- 迭代器:顺序访问聚合对象的元素,不暴露内部表示。
- 中介者:用中介对象封装一系列对象交互,使对象不必显式相互引用。
- 备忘录:保存对象当前状态,以便之后恢复。
- 观察者:一对多依赖,状态变化时通知所有依赖者。
- 状态:对象行为随内部状态改变而改变。
- 策略:封装一系列算法,使它们可相互替换。
- 模板方法:定义算法骨架,某些步骤延迟到子类。
- 访问者:作用于某对象结构中各元素的操作,可以在不改变元素类的前提下定义新操作。
(45) 设计模式将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
查看答案与解析收起答案与解析
选 A(命令)。题干就是命令模式的定义原文:“将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。”
B 责任链:请求沿链传递,谁处理不确定。C 观察者:状态变化通知依赖者。D 策略:封装可替换算法。
命令模式的三个附加能力要记住:排队(把命令对象放进队列)、日志(记录执行过的命令)、撤销(undo,反向执行)。这三点是它区别于“直接调用方法”的价值所在。
角色有四个:Command(抽象命令)、ConcreteCommand(具体命令)、Invoker(调用者)、Receiver(接收者)。GUI 的按钮点击、事务回滚都是典型应用。
查看答案与解析收起答案与解析
(44)选 A(命令)。图里 Operation 是命令接口,BuyOperation/SellOperation/ViewOperation 是具体命令,Stock 是接收者(被操作的对象),Broker 是调用者——股票交易的标准命令模式。
(45)选 A(Operation)。Broker 直接调用的是抽象命令接口 Operation,而不是某个具体命令,这样换命令时 Broker 的代码不用改。
(46)选 D(Stock)。命令的真正执行落在接收者身上:Buy/Sell 操作最终都要读写 Stock(股票)的数据,Stock 是 Receiver 角色。
(47)选 B。具体命令继承 Operation 并实现 execute,图中对应 SellOperation、BuyOperation、ViewOperation 三个。
四空正好覆盖命令模式的四个角色:抽象命令、具体命令、调用者、接收者。看图认角色时,先找“接口 + 三个实现”,再看谁持有接口的引用。
查看答案与解析收起答案与解析
(44)选 D(中介者)。题干是中介者的标准描述:一组对象以复杂方式通信、相互依赖难以理解——用中介者封装这一系列对象交互,使各对象不需要显式相互引用。
(45)选 C(行为型对象)。中介者属于行为型模式,且通过对象组合(各同事对象持有中介者引用)实现,所以是“行为型对象模式”。
(46)选 D。中介者的适用场景是“一组对象以定义良好但复杂的方式进行通信”。选项 D 的完整表述对应教材原文。
A 算法变体是策略的场景;B 语言解释是解释器的场景。这两个都是拿别的模式意图来混淆。
中介者的形象理解:机场塔台。所有飞机只跟塔台通信,飞机之间不直接对话——通信复杂度从 n(n−1)/2 条两两连线,降成 n 条到塔台的连线。
查看答案与解析收起答案与解析
(46)选 D(观察者)。题干描述“一个对象状态改变时要通知所有依赖它的对象”——一对多依赖、自动通知,这是观察者的定义。
(47)选 A。观察者模式的结构约束:一个 Subject(被观察者)可以对应多个 Observer(观察者),观察者通过 Subject.attach() 注册自己。
B 错:Subject 的子类(ConcreteSubject)可以有很多个,没有“只能一个”的限制。
C 错:Observer 的子类同样可以有多个。
D 错:“必须至少对应一个”说反了——没有观察者时 Subject 照样工作,只是通知没人收。
观察者两个角色记住:Subject 管“通知”,Observer 管“响应”;关系是 Subject 持有一组 Observer。
查看答案与解析收起答案与解析
(46)选 D(观察者)。题干描述“一个对象状态改变时通知依赖它的对象”。
(47)问“错误的”,选 B。“ConcreteObserver 可以绕过 Subject 及其子类的封装”——观察者模式不仅不绕过封装,反而依赖封装:Subject 只暴露 attach/detach/notify 接口,观察者只能收到通知,不能直接改 Subject 的状态。
A 错的表述方向对——实际是 Subject 维护 Observer 列表,不是 Observer 维护 Subject,A 这句话本身就不成立(作为“错误项”候选)。C 正确:一个 Subject 需要维护多个 Observer(一对多)。D 正确:Subject 状态变化时调用 notify 通知所有 Observer。
这题的判定要点:封装是观察者模式的立身之本,“绕过封装”的说法一定错。
查看答案与解析收起答案与解析
(40)选 C(观察者)。题干描述“一个对象状态改变时通知所有依赖它的对象”——观察者定义。
(41)选 B(ConcreteSubject)。图中 Subject 是抽象被观察者(维护观察者列表、提供 notify),ConcreteSubject 是具体被观察者——它的状态变化会触发通知。题干说“在其状态发生改变时通知观察者”,指的是具体被观察者干的事。
A Subject 是抽象角色,C Observer 和 D ConcreteObserver 是观察者一侧,负责接收通知并更新自己。
观察者的角色分工记牢:Subject 定接口,ConcreteSubject 存状态发通知;Observer 定接口,ConcreteObserver 收通知做响应。
在发布-订阅(Publish-Subscribe)消息模型中,订阅者订阅一个主题后,当该主题有新消息到达时,所有订阅者都会收到通知。 (45) 设计模式最适合这一模型。
查看答案与解析收起答案与解析
选 D(观察者)。发布-订阅(Publish-Subscribe)模型:订阅者注册主题 → 主题有新消息 → 所有订阅者收到通知。这正是观察者模式的应用场景——发布者就是 Subject,订阅者就是 Observer。
A 适配器:转换接口。B“通知(Notifier)”不是 GoF 模式,是造出来的词。C 状态:对象行为随内部状态改变。
发布-订阅和观察者的关系:发布-订阅是观察者模式在消息中间件里的实现形态(Redis 的 pub/sub、Kafka 的 consumer group),核心机制——注册、事件触发、广播——完全一致。
看到“订阅”“注册”“通知所有”这一组词,直接锁定观察者。
(46) 设计模式最适合用于发布/订阅消息模型,即当订阅者注册一个主题后,此主题有新消息到来时订阅者就会收到通知。
查看答案与解析收起答案与解析
选 C(观察者)。“订阅者注册一个主题,主题有新消息到来时订阅者就会收到通知”——与真题 28 的描述几乎一字不差,还是观察者模式。
A 适配器:转换接口。B“通知(Notifier)”不是标准模式名,纯干扰。D 状态:对象内部状态改变时行为改变,不涉及“通知别人”。
这道题(2016 上)与 2013 下(真题 28)、2019 上(真题 31)三年三考同一个场景,说明“发布/订阅”这个词组与“观察者模式”的对应关系是必背项。
顺带区分两个词:观察者强调“依赖关系”,发布-订阅强调“消息通道”——前者 Subject 直接知道 Observer,后者中间通常隔着一个事件总线。
查看答案与解析收起答案与解析
(44)选 A(Subject)。图中 Subject 是被观察者:它维护一个观察者列表,提供 attach(注册)、detach(注销)、notify(通知)操作;ConcreteSubject 是具体被观察者,存着会被观察的状态。
(45)选 B。观察者模式体现的设计原则是“使所要交互的对象尽量松耦合”——Subject 和 Observer 之间只靠一个抽象接口连接,双方可以独立变化,互不知道对方的具体类型。
A“对扩展开放、对修改关闭”是开闭原则,观察者确实符合,但题目问的是它体现的耦合特性,B 更直接。C 组合优于继承、D 迪米特法则(只与直接关联类交互)都是别的原则。
观察者的松耦合体现在:Subject 只认识 Observer 接口,不认识任何具体观察者——新增一种观察者,Subject 一行代码不用改。
观察者(Observer)模式适用于 (47) 。
查看答案与解析收起答案与解析
选 D。观察者适用于“一对多对象依赖关系,当一个对象修改后,依赖它的对象都自动得到通知”——这是意图原句。
A 错:“访问聚合对象的内容而无须暴露内部表示”是迭代器的意图。
B 错:“减少多个对象或类之间的通信复杂性”是中介者的意图。
C 错:“将对象的状态恢复到先前的状态”是备忘录的意图。
四个选项正好是四个行为型模式各自的意图句,属于“意图句辨认”的标准考法。行为型 11 个模式里,迭代器、中介者、备忘录这三个的意图句最常被拿来和观察者配对出题,建议成组记忆。
查看答案与解析收起答案与解析
(44)选 D(状态)。题干描述“对象的行为随内部状态改变而改变”——这正是状态模式的定义:把不同状态下的行为封装到不同的状态类中,对象的状态切换时行为跟着切换。
(45)选 C(TCPState)。状态模式的三个角色:Context(上下文,持有状态引用)、State(抽象状态)、ConcreteState(具体状态)。图中 TCPConnection 是 Context,TCPState 是 State 接口,TCPEstablished/TCPListen/TCPClosed 是具体状态。
(46)选 A(TCPConnection)。它是 Context 角色——持有当前状态对象的引用,把请求转发给状态对象处理。
把 TCP 状态机套进模式里看:连接的建立、监听、关闭是三种状态,每种状态下收到同样的报文行为不同——这正是“行为随状态改变”的教科书演示。
查看答案与解析收起答案与解析
(44)选 B(状态)。题干描述自动售货机的行为随“库存、货币量、找零能力、所选项目”这些内部状态变化——典型的状态模式场景。
(45)选 B(VendingMachineState)。它是图中所有具体状态(VendingDepositState 存币、VendingStockState 库存等)共同实现的状态接口。
(46)选 D(行为型对象模式)。状态模式属于行为型,且通过 Context 持有 State 对象引用(组合)实现,是对象模式。
(47)选 C。状态模式与策略模式的类图几乎一模一样(Context 持有一个策略/状态接口的引用),区别只在意图:策略是让客户端选算法,状态是让对象自己随内部状态切换行为——状态模式的状态切换通常由状态类自己触发。
“状态图和策略图相同、意图不同”是本章最容易考的辨析点,务必分清。
(44) 设计模式定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。这一模式使得算法可独立于它的客户而变化。
查看答案与解析收起答案与解析
选 A(策略)。题干是策略模式的定义原句:“定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。这一模式使得算法可独立于它的客户而变化。”
B 抽象工厂:创建一系列相关对象。C 观察者:状态变化通知依赖者。D 状态:行为随内部状态改变。
策略和状态是“同图不同义”的一对:类图都是 Context 持有一个抽象接口的引用。区别在谁决定切换——策略由客户端选(用户选促销方式),状态由对象内部自动切(TCP 连接自己变状态)。
策略模式的典型应用:排序算法可替换、支付方式可替换、压缩算法可替换。Java 的 Comparator 就是策略接口。
(44) 模式定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,使得算法可以独立于使用它们的客户而变化。以下 (45) 情况适合选用该模式。(2016年下半年)<br/>①一个客户需要使用一组相关对象<br/>②一个对象的改变需要改变其它对象<br/>③需要使用一个算法的不同变体<br/>④许多相关的类仅仅是行为有异
查看答案与解析收起答案与解析
(44)选 D(策略)。同真题 34 的定义原句。
(45)选 C(③④)。逐条判断:
① 一个客户需要使用一组相关对象——这是抽象工厂的场景(一族产品一起创建)。
② 一个对象的改变需要改变其它对象——这是观察者的场景。
③ 需要使用一个算法的不同变体——策略正是把每个变体封装成一个策略类,运行时替换。属于。
④ 许多相关的类仅仅是行为有异——这正是策略的典型起点:一堆类结构相同、只有算法不同,干脆抽成一个类 + N 个策略。属于。
所以 ③④ 成立,选 C。①② 的意图句分别属于另外两个模式,用排除法也能做。
在设计某购物中心的收银软件系统时,要求能够支持在不同时期推出打折、返利、满减等不同促销活动,则适合采用 (47) 模式。
查看答案与解析收起答案与解析
选 A(策略)。“支持在不同时期推出打折、返利、满减等不同促销活动”——每种促销是一种算法,时期不同换一种用,这正是策略模式:促销策略封装成类,收银系统只面向抽象策略编程。
B 访问者:对对象结构中的元素定义新操作,用于结构固定、操作多变。C 观察者:状态变化通知依赖者。D 中介者:封装多对象交互。
如果不加封装,收银代码里会堆满 if(type==‘打折’)…else if(type==‘返利’)…——每加一种促销要改主流程,违反开闭原则。用策略后,新增促销就是新增一个类,主流程不动。
这道题(2021 下)同时还是中介者模式的题干背景(同一场考试的 44-46 题),说明命题人喜欢拿“购物中心”这个场景连续出题,复习时把一个场景配多个模式来理解会更有效率。
某些设计模式会引入总是被用作参数的对象。例如 (47) 对象是一个多态accept方法的参数。
查看答案与解析收起答案与解析
选 A(Visitor)。“某些设计模式会引入总是被用作参数的对象”——说的是访问者模式的双分派机制:元素类定义 `accept(Visitor v)` 方法,访问者对象作为参数传进去,然后调用 `v.visit(this)`。
B Command:命令对象被 Invoker 持有并 execute,不是作为 accept 的参数。C Memento:备忘录对象由 Originator 创建、由 Caretaker 保存。D Observer:观察者被 Subject 注册和通知。
双分派是访问者的标志:第一次分派发生在 `element.accept(visitor)`(由元素的动态类型决定),第二次发生在 `visitor.visit(this)`(由访问者的动态类型决定)。两次分派叠加,就能对“对象结构 × 操作”做任意组合。
适用于结构稳定、操作多变的场景:编译器的 AST 节点固定,但“类型检查、代码生成、美化打印”等操作会不断增加。
查看答案与解析收起答案与解析
(44)选 B(访问者)。题干描述超市销售系统:物品(Item)加入购物车(Shopping_Cart),收银台(Checkout,人工或自动)把价格汇总——收银台以不同的方式“访问”购物车里的每种物品,这正是访问者模式。
(45)选 A(Item)。访问者模式里被访问的元素叫 Element,图中 Item 就是它——accept 方法的定义者。
(46)选 D(行为型对象模式)。访问者属于行为型,且通过对象组合实现,是对象模式。
(47)选 C(Checkout)。图中充当 Visitor 角色的是 Checkout——它实现对每种 Item 的 visit 操作(人工收银 Manual 和自动收银 Auto 是它的两个变体)。
访问者模式的结构记四点:Visitor 定操作,Element 定 accept,ObjectStructure 管集合,双分派做组合。
五、综合对号入座:一句话辨认模式
剩下 8 道题是“多模式混考”:一道题里连问三四个模式。这类题没有新知识,考的就是上面各节的意图背得熟不熟。做错的题建议回到对应小节,把那个模式的意图句重背一遍。
当不适合采用生成子类的方法对己有的类进行扩充时,可以采用 (43) 设计模式动态地给一个对象添加一些额外的职责;当应用程序由于使用大量的对象,造成很大的存储开销时,可以采用 (44) 设计模式运用共享技术来有效地支持大量细粒度的对象;当想使用一个已经存在的类,但其接口不符合需求时,可以采用 (45) 设计模式将该类的接口转换成我们希望的接口。
查看答案与解析收起答案与解析
(43)选 C(装饰)。“不适合采用生成子类的方法扩充类、动态地给对象添加额外职责”——装饰器的标准描述。生成子类是静态的、类数量爆炸;装饰是动态的、运行时随意组合。
(44)选 D(享元)。“使用大量对象造成很大存储开销,运用共享技术支持大量细粒度对象”——享元的定义,共享内部状态省内存。
(45)选 B(适配器)。“想使用一个已经存在的类,但其接口不符合需求,把接口转换成希望的接口”——适配器的定义。
三个空分别对应结构型三兄弟的“身份证”:装饰器管加职责、享元管省内存、适配器管换接口。这一道题等于把结构型模式的意图句抽查了一遍,全对才算过关。
采用 (43) 设计模式可保证一个类仅有一个实例;采用 (44) 设计模式可将对象组合成树形结构以表示“部分-整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性;采用 (45) 设计模式可动态地给一个对象添加一些额外的职责。
查看答案与解析收起答案与解析
(43)选 B(单例)。“保证一个类仅有一个实例”——单例的定义。
(44)选 D(组合)。“可将对象组合成树形结构以表示‘部分-整体’的层次结构,使用户对单个对象和组合对象的使用具有一致性”——组合的定义,注意“单个对象和组合对象使用一致”这个特征句。
(45)选 C(装饰)。“动态地给一个对象添加一些额外的职责”——装饰器的定义。
一道题串三个模式(创建型、结构型、结构型),说明综合题的出题方式就是把几个模式的意图句拼接成一段话。对付它的方法:把每个空单独拎出来,按意图句对号,三个独立的小题而已。
设计模式根据目的进行分类;可以分为创建型、结构型和行为型三种。其中结构型模式用于处理类和对象的组合。 (44) 模式是一种结构型模式。
查看答案与解析收起答案与解析
选 A(适配器)。题目先给了分类框架:创建型管生成、结构型管组装、行为型管交互,然后问哪个是结构型。
A 适配器:把接口转换成另一个接口,处理的是“类和对象如何组装成更大的结构”,属于结构型。
B 命令:把请求封装成对象,行为型。C 生成器:逐步构建复杂对象,创建型。D 状态:行为随内部状态改变,行为型。
四个选项每个分类各占一个,说明命题人就是冲着“三分类”来的。这张分类表必须背死:
创建型:单例、原型、生成器、工厂方法、抽象工厂。
结构型:适配器、桥接、组合、装饰器、外观、享元、代理。
行为型:其余 11 个。
(44) 设计模式能使一个对象的状态发生改变时通知所有依赖它的监听者。 (45)设计模式限制类的实例对象只能有一个。适配器(Adapter)设计模式可以用于 (46) 。用于为一个对象添加更多功能而不使用子类的是 (47) 设计模式。
查看答案与解析收起答案与解析
(44)选 D(观察者)。“能使一个对象的状态发生改变时通知所有依赖它的监听者”——观察者的意图。
(45)选 C(单例)。“限制类的实例对象只能有一个”——单例的意图。
(46)选 A。适配器“将已有类的接口转换成和目标接口兼容”——它解决接口不兼容问题。B 改进性能、C 生成文档、D 实现 Undo 都不是它的用途。
(47)选 D(装饰器)。“为一个对象添加更多功能而不使用子类”——装饰器与继承的关键区别:继承是静态的、类数量爆炸;装饰器是动态的、运行时自由组合。
四个空四个模式,全是意图句辨认。这道题再次说明:本章 90% 的分数来自“意图句 → 模式名”的条件反射。
下列设计模式中, (46) 模式既是类结构型模式,又是对象结构型模式。此模式与 (47) 模式类似的特征是,都给另一个对象提供了一定程度上的间接性,都涉及从自身以外的一个接口向这个对象转发请求。
查看答案与解析收起答案与解析
(46)选 B(适配器)。适配器有一个特殊地位:既是类结构型模式,又是对象结构型模式——类适配器用多重继承实现(继承被适配类+实现目标接口),对象适配器用组合实现(持有被适配对象引用)。其他模式都只有对象形式。
(47)选 A(桥接)。题干说“与(47)模式类似的特征是,都给另一个对象提供了一定程度上的间接性,都涉及从自身以外的一个接口向这个对象转发请求”——适配器和桥接都通过“转发请求”给另一个对象工作。
但两者意图不同:适配器是事后补救(接口已经不兼容了才转换),桥接是事先设计(一开始就把抽象和实现分开,让它们独立扩展)。
C 组合是部分—整体,D 装饰器是加职责,都不涉及“接口转发”这个特征。
一组对象以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。采用 (43) 模式,用一个中介对象来封装一系列的对象交互,从而使各对象不需要显式地相互引用,使其耦合松散,而且可以独立地改变它们之间的交互。此模式与 (44) 模式是相互竞争的模式,主要差别是:前者的中介对象封装了其他对象间的通信,而后者通过引入其他对象来分布通信。
查看答案与解析收起答案与解析
(43)选 C(中介者)。“一组对象以定义良好但复杂的方式通信,相互依赖结构混乱”——用中介者封装一系列对象交互,使各对象不需要显式相互引用。这正是中介者的意图原句。
(44)选 D(观察者)。题干后半句点破了两者的关系:“此模式与(44)模式是相互竞争的模式,主要差别是:前者的中介对象封装了其他对象间的通信,而后者……”
观察者模式里,Subject 直接把消息广播给所有 Observer,对象之间仍然知道彼此(至少知道 Subject);中介者模式里,同事对象之间完全不知道对方,所有通信都经过中介者中转。
选择依据:多对象两两交互、关系复杂 → 中介者;一对多的状态通知 → 观察者。两者都能把网状通信变星形,但中介者封顶了复杂度,观察者保留了广播能力。
(45) 设计模式能够动态地给一个对象添加一些额外的职责而无需修改此对象的结构; (46) 设计模式定义一个用于创建对象的接口,让子类决定实例化哪一个类;欲使一个后端数据模型能够被多个前端用户界面连接,采用 (47) 模式最适合。
查看答案与解析收起答案与解析
(45)选 D(装饰器)。“能够动态地给一个对象添加一些额外的职责而无需修改此对象的结构”——装饰器的定义原句,注意“无需修改结构”对应“不是用子类继承的方式”。
(46)选 A(工厂方法)。“定义一个用于创建对象的接口,让子类决定实例化哪一个类”——工厂方法的定义,工厂方法把“实例化推迟到子类”。
(47)选 D(桥接)。“使一个后端数据模型能够被多个前端用户界面连接”——数据模型是一个维度,前端界面是另一个维度,两个维度独立扩展,这是桥接的标准场景:模型侧一个抽象层次,界面侧多个实现,中间用桥接连接。
A 装饰器是加职责、B 享元是省内存、C 观察者是通知,都不解决“一后端多前端”的结构问题。
以下设计模式中, (44) 模式使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理为止,从而避免请求的发送者和接收者之间的耦合关系; (45) 模式提供一种方法顺序访问一个聚合对象中的各个元素,且不需要暴露该对象的内部表示。这两种模式均为 (46) 。
查看答案与解析收起答案与解析
(44)选 A(责任链)。“使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理为止,从而避免请求的发送者和接收者之间的耦合”——责任链的定义原句。
(45)选 D(迭代器)。“提供一种方法顺序访问一个聚合对象中的各个元素,且不需要暴露该对象的内部表示”——迭代器的定义原句。
(46)选 C(行为型对象模式)。责任链和迭代器都属于行为型,且都用对象组合(链上节点持有后继引用、迭代器持有聚合引用)实现。
这两者的共同点是“沿结构逐个走”:责任链沿处理者链找处理者,迭代器沿集合走元素。区别在目的:责任链找“谁来处理”,迭代器管“怎么遍历”。
六、答案速查表
| 真题 | 年份 | 答案 | 真题 | 年份 | 答案 |
|---|---|---|---|---|---|
| 1 | 2011 上 | A | 24 | — | D、C、D |
| 2 | — | C、D | 25 | — | D、A |
| 3 | 2016 上 | B | 26 | — | D、B |
| 4 | — | B、C | 27 | — | C、B |
| 5 | — | A、A | 28 | 2013 下 | D |
| 6 | — | A、C、A、D | 29 | 2016 上 | C |
| 7 | 2011 上 | D | 30 | — | A、B |
| 8 | 2012 下 | C | 31 | 2019 上 | D |
| 9 | 2015 下 | D | 32 | — | D、C、A |
| 10 | 2011 上 | A | 33 | — | B、B、D、C |
| 11 | — | A、D | 34 | 2013 下 | A |
| 12 | — | D、A、B、A | 35 | — | D、C |
| 13 | — | C、D、A、B | 36 | 2021 下 | A |
| 14 | — | C、D、A | 37 | 2015 上 | A |
| 15 | — | C、A | 38 | — | B、A、D、C |
| 16 | — | C、B、A | 39 | 2009 上 | C、D、B |
| 17 | 2011 下 | D | 40 | 2009 下 | B、D、C |
| 18 | — | C、D | 41 | 2012 上 | A |
| 19 | — | B、D | 42 | 2013 上 | D、C、A、D |
| 20 | 2016 上 | B | 43 | 2014 上 | B、A |
| 21 | 2011 下 | B | 44 | 2014 下 | C、D |
| 22 | 2016 上 | A | 45 | 2015 下 | D、A、D |
| 23 | — | A、A、D、B | 46 | 2019 上 | A、D、C |
写在最后
设计模式这一章的本质是词汇量:23 个模式名就是 23 个单词,意图句就是释义。方法和其他章节完全不同——不需要理解推导,只需要反复对号。
建议的复习节奏:
- 先把本章每节开头的“意图句”抄一遍,只抄模式名和那一句话。
- 合上资料,看意图句回忆模式名,错的做标记。
- 只重做带标记的题,直到 3 秒出答案。
考试时先看动词:看到“创建一系列”想抽象工厂,“逐步构建”想生成器,“树形部分整体”想组合,“动态加职责”想装饰器,“简化接口”想外观,“共享细粒度”想享元,“同接口控制访问”想代理,“通知所有依赖者”想观察者,“封装请求成对象”想命令,“一系列可替换算法”想策略。这些动词短语就是命题人的指纹。
还没有评论 — 第一条由你来留。