2025 年软件系统设计考题解答¶
本文件包含两套来源的 2025 年考题解答:
- 第一部分(Part A):来自
exams/软件系统设计-复习-EagleBear.pdf中标记为【2025】的考题——由复习资料按 2025 标签汇总,偏架构通识类。 - 第二部分(Part B):来自
exams/2025回忆.md——真实考卷的学长回忆版,权威性更高,偏设计模式与微服务设计。
两套题目几乎不重叠,建议以 Part B 为重点复习,Part A 作为补充。
Part A(EagleBear 复习资料 · 2025 标签汇总)¶
1. Architecture、Structure 和 Design 的区别?¶
- Design 包含 Architecture,Architecture 包含 Structure
- 所有架构都是设计,但不是所有设计都是架构。体系结构是软件设计的一个部分
- 结构(Structure) 是静态的、逻辑的,关于系统如何构成
- 架构(Architecture) 除包含结构,还包含组件之间的相关关系结构,并定义一些动态行为
- 程序或计算系统的软件架构是系统的一个或多个结构,包括软件元素、这些元素的外部可见属性以及它们之间的关系
参考课件:
slides/2026SUG_SysArch1_introduction.pdf
2. 在设计软件时应用了哪些通用设计策略?为每个策略提供简明工作示例¶
六种通用设计策略:
| 策略 | 示例 |
|---|---|
| 分解(Decomposition) | 将系统分解为前端、业务逻辑、数据访问三层 |
| 抽象(Abstraction) | 将系统抽象为组件和连接器 |
| 逐步求精/分而治之 | 对每个模块分别设计,先定义接口再细化实现 |
| 生成与测试(Generate and Test) | 设计评审后针对关键路径生成测试用例 |
| 迭代-增量细化 | ADD 方法多次迭代直到满足所有 ASR |
| 复用元素(Reuse of Elements) | 重用现有架构模式(MVC、分层、消息队列等) |
参考课件:
slides/Lecture 01 - Attributes Driven Design.pdf
3. 通用设计策略在 ADD 中如何体现?¶
ADD(Attribute-Driven Design,属性驱动设计)作为一个迭代的架构设计方法,体现了全部六种通用设计策略:
| 策略 | 在 ADD 中的体现 |
|---|---|
| 分解 | Step 2:选择要分解的系统要素,Step 5:实例化架构元素并分配职责 |
| 抽象 | Step 3:确定 ASR 时关注抽象的需求而非实现,Step 4:选择设计决策时使用抽象的模式和战术 |
| 逐步求精/分而治之 | Step 5-7:逐一为每个实例化元素分配职责、定义接口、验证需求 |
| 生成与测试 | Step 4-⑥:评估并解决不一致的问题,验证设计是否满足 ASR |
| 迭代-增量细化 | Step 8:重复 Step 2-7,每次迭代处理一组 ASR,直到所有 ASR 都满足 |
| 复用元素 | Step 4-③:从候选模式/决策清单中选择已有的架构模式和战术 |
ADD 的核心循环体现了迭代思维:识别 ASR → 选择设计决策 → 验证 → 确认 → 下一组 ASR。
参考课件:
slides/Lecture 01 - Attributes Driven Design.pdf(ADD),slides/Lecture 02 - Attributes Driven Design - Case Study.pdf
4. 描述 4+1 视图¶
五种视图: - 逻辑视图(Logical View):描述对架构重要的元素及其关系(功能需求),面向最终用户 - 过程视图(Process View):描述元素间的并发和交互,面向系统集成者 - 开发视图(Development View):描述软件模块的内部组织联系,面向开发团队 - 物理视图(Physical View):描述过程和组件如何映射到硬件,面向系统工程师 - +1 场景/用例视图(Scenarios):通过用例场景验证和说明架构设计,面向所有利益相关者
参考课件:
slides/2026SUG_SysArch1_introduction.pdf
5. 请将"可用性"定义为质量属性。MTBF 和 MTTR 代表什么?如何计算可用性? (6分)¶
可用性定义¶
可用性(Availability) 是系统在指定的时间间隔内,能够在要求的范围内提供指定服务的能力。可将可用性计算为在指定时间间隔内系统能提供指定服务的概率。
MTBF 和 MTTR¶
| 缩写 | 全称 | 含义 |
|---|---|---|
| MTBF | Mean Time Between Failures | 平均无故障时间——系统正常运行的平均时间 |
| MTTR | Mean Time To Repair | 平均维修时间——系统从故障中恢复所需的平均时间 |
计算公式¶
SLA 等级示例: - 99%(两个9)→ 年停机约 3.65 天 - 99.9%(三个9)→ 年停机约 8.76 小时 - 99.99%(四个9)→ 年停机约 52.6 分钟 - 99.999%(五个9)→ 年停机约 5.26 分钟
参考课件:
slides/2026SUG_SysArch2_quality attributes.pdf
6. Peer-to-Peer 模式¶
Peer-to-Peer 模式(对等模式) 是一种 C&C 架构风格。
特点¶
- 每个节点(Peer)既是客户端也是服务器
- 节点之间直接通信,没有中心化服务器
- 每个节点可以请求服务,也可以提供服务
- 高度去中心化
适用场景¶
- 文件共享系统(如 BitTorrent)
- 区块链网络
- 分布式计算系统
优势与限制¶
| 优势 | 限制 |
|---|---|
| 高可伸缩性,节点越多能力越强 | 安全性难以保证 |
| 无单点故障 | 管理和协调复杂 |
| 资源利用率高 | 性能不可预测 |
参考课件:
slides/2026SUG_SysArch1_introduction.pdf(C&C 风格)
7. Pipe-Filter 模式¶
管道-过滤器模式(Pipe-Filter Pattern) 是一种 C&C 架构风格。
特点¶
- 过滤器(Filter):执行数据处理的计算组件
- 管道(Pipe):在过滤器之间传输数据的连接器
- 每个过滤器独立,不共享状态
- 数据流经管道依次被每个过滤器处理
适用场景¶
- 数据流处理系统
- 编译器(词法分析 → 语法分析 → 语义分析 → 代码生成)
- ETL(Extract-Transform-Load)管道
优势与限制¶
| 优势 | 限制 |
|---|---|
| 过滤器可独立开发和测试 | 不适合交互式系统 |
| 易于扩展(添加新过滤器) | 数据格式转换开销 |
| 支持并行处理 | 错误处理复杂 |
参考课件:
slides/软件架构模式_update_2.pdf(管道-过滤器模式)
8. 微服务架构(MSA)和 SOA 的区别、相同点¶
相同点¶
- 都是分布式架构,微服务是 SOA 的扩展
- 都包含服务契约、服务封装、服务重用、服务组合、服务自治、服务无状态等基本特点
区别¶
| 维度 | SOA | MSA(微服务) |
|---|---|---|
| 服务间通信 | 智能管道(ESB),重量级协议(SOAP/WS-*) | 哑管道(消息代理),轻量级协议(REST/gRPC) |
| 数据管理 | 全局数据模型,共享数据库 | 每个服务独立数据模型和数据库 |
| 服务规模 | 较大的单体应用 | 较小的服务 |
| 中间件 | 使用 ESB | 去掉 ESB,轻量级通信 |
| DevOps | 传统部署 | 自动化部署和监控预警 |
| 额外机制 | — | API Gateway(屏蔽服务复杂性)、熔断器(避免级联故障) |
参考课件:
slides/Microservices Patterns.pdf,slides/软件架构模式_update_2.pdf
9. 微服务架构的六大特性 (6分)¶
- 通过服务组件化(Componentization via Services):将系统拆分为独立的服务组件,每个服务可独立部署
- 围绕业务能力组织(Organized around Business Capabilities):而非聚焦在技术层面,服务按业务领域划分
- 服务内部高内聚,服务间低耦合(High Cohesion, Loose Coupling):遵循单一职责原则
- 去中心化(Decentralized):去中心化的数据管理和治理
- 基础设施自动化(Infrastructure Automation):自动化测试、部署、监控
- 服务高可用性设计、演进式设计(Evolutionary Design):容错设计,允许系统持续演进
其他特性: - 每个服务相对较小并容易维护 - 服务可以独立扩展 - 技术栈不受限 - 大型复杂程序可以持续部署和交付 - 高可伸缩
参考课件:
slides/Microservices Patterns.pdf
10. 企业架构的核心是什么?它们在企业数字化转型中发挥什么作用? (6分)¶
企业架构的核心¶
企业架构(Enterprise Architecture, EA)的核心包括:
- 业务架构(Business Architecture):描述业务战略、组织结构和业务流程
- 数据架构(Data Architecture):描述企业数据资产的管理和使用
- 应用架构(Application Architecture):描述应用系统的结构和交互
- 技术架构(Technology Architecture):描述支撑应用和数据的技术基础设施
在数字化转型中的作用¶
- 战略对齐:确保 IT 投资和技术决策与企业的业务战略保持一致
- 标准化与整合:建立技术标准和规范,消除信息孤岛,促进系统间的互操作性
- 变更管理:为数字化转型提供路线图和过渡规划,管理从现状到目标状态的演进
- 风险管控:识别和管理技术债务、安全风险和合规问题
- 决策支持:为管理层提供全面的 IT 全景视图,支撑投资和建设决策
- 创新能力:通过架构框架识别新技术的引入机会,支持业务模式创新
参考课件:
slides/2026SUG_SysArch1_introduction.pdf(企业架构)
11. 材料题:电商平台 Java 代码 DDD 分析¶
场景:给出一个电商平台 Java 代码,要求: 1. (8分) 指出两处违反 DDD 原则之处 2. (12分) 改进设计以符合 DDD 原则
常见违反 DDD 原则的模式¶
违反 1:贫血模型(Anemic Domain Model) - 表现:领域对象只有 getter/setter,没有业务逻辑,业务逻辑全在 Service 层 - 违反原则:DDD 要求领域模型应包含业务逻辑(Entity 应有行为方法)
违反 2:缺少限界上下文边界 - 表现:不同业务概念(如订单、支付、库存)的代码混在一起,没有明确的上下文边界 - 违反原则:DDD 要求通过限界上下文(Bounded Context)隔离不同领域
违反 3:没有统一语言 - 表现:代码中同一概念使用不同名称,或不同概念使用相同名称 - 违反原则:DDD 要求团队使用统一的领域语言
改进方案(DDD 风格)¶
// 改进 1:充血模型 — 将业务逻辑放入领域实体
// 违反前(贫血模型):
class Order {
private String id;
private BigDecimal total;
private String status;
// 只有 getter/setter
}
class OrderService {
public void cancelOrder(Order order) {
if ("paid".equals(order.getStatus())) {
order.setStatus("cancelled");
// 退款逻辑...
}
}
}
// 改进后(充血模型):
class Order {
private OrderId id;
private Money total;
private OrderStatus status;
public void cancel() {
if (!status.canBeCancelled()) {
throw new OrderException("当前状态不可取消");
}
this.status = OrderStatus.CANCELLED;
// 发布领域事件:OrderCancelledEvent
}
}
// 改进 2:划分限界上下文
// ┌──────────┐ ┌──────────┐ ┌──────────┐
// │ 订单上下文 │ │ 支付上下文 │ │ 库存上下文 │
// │ Order │ │ Payment │ │ Inventory│
// │ Bounded │◀─▶│ Bounded │◀─▶│ Bounded │
// │ Context │ │ Context │ │ Context │
// └──────────┘ └──────────┘ └──────────┘
// 改进 3:使用统一语言 + 值对象
class OrderId {
private final String value;
// 封装订单ID的格式验证
}
class Money {
private final BigDecimal amount;
private final Currency currency;
// 封装金额计算逻辑
}
DDD 改进要点总结¶
| 改进点 | 违反的 DDD 原则 | 改进方式 |
|---|---|---|
| 贫血模型 → 充血模型 | 领域模型应包含业务逻辑 | 将 Service 层逻辑迁移到 Entity/Value Object 中 |
| 缺乏限界上下文 | 明确的模型边界 | 按业务能力划分 Bounded Context |
| 原始类型迷恋 | 使用值对象(Value Object) | 用 OrderId、Money 等值对象替代 String、BigDecimal |
| 命名不一致 | 统一语言(Ubiquitous Language) | 统一业务术语命名 |
参考课件:
exams/软件系统设计-复习-EagleBear.pdf(DDD 参考),slides/Lecture 01 - Attributes Driven Design.pdf
综合参考:
exams/软件系统设计-复习-EagleBear.pdf(综合复习资料)
Part B(2025 真实考卷 · 学长回忆版)¶
来源:
exams/2025回忆.md(荣耀笔记回忆) 注:以下为考后回忆整理,题干表述可能与真实考卷有出入,但考点覆盖可靠。
一、简答题¶
B1. 什么需求影响架构,怎么得到?¶
影响架构的需求类型(三类):
- 功能需求(Functional Requirements):说明系统必须做什么、如何为利益相关者提供价值。功能需求通常决定架构的元素(components/connectors),但一般不唯一决定架构——同一组功能可由多种架构实现。
- 质量需求(Quality Attribute Requirements):系统的整体理想属性,如性能、可用性、安全性、可修改性、可测试性等。质量需求是决定架构形态的主要因素,不同的质量需求会导致截然不同的架构。
- 约束(Constraints):预先确定的设计决策,包括技术选型、行业法规、标准、已有系统集成、团队结构等。约束缩小了设计空间。
其中,质量需求和约束对架构的影响最大;功能需求决定"做什么",质量需求和约束决定"怎么做"。
如何得到这些需求(ASR 收集与识别):
| 来源 | 方法 |
|---|---|
| 需求文档(Requirement Docs) | MoSCoW 方法(Must/Should/Could/Won't)、用户故事 |
| 质量属性工作坊(QA Workshop) | 采访涉众,编制质量属性场景 |
| 业务目标(Business Goals) | 与管理层沟通,明确业务驱动力 |
| 效用树(Utility Tree) | 按 重要性 × 难度 对场景排序,识别最关键的 ASR |
| 架构驱动因素分析 | 结合上述来源确定 ASR(架构攸关需求) |
参考课件:
slides/2026SUG_SysArch1_introduction.pdf(需求类型),slides/2026SUG_SysArch2_quality attributes.pdf(质量属性);详见src/2016.mdQ3、src/review.md§3.1 / §4.2
B2. 如何描述质量属性场景?描述可操作性和性能的场景¶
质量属性场景建模:用六元组描述一个具体的质量属性需求,使其可分析、可验证。
刺激源(Source) ──▶ 刺激(Stimulus) ──▶ 工件(Artifact)
│
▼
环境(Environment) ◀── 响应(Response) ◀── 度量(Response Measure)
| 要素 | 含义 |
|---|---|
| 刺激源 Source | 产生刺激的实体(用户、外部系统、监视器等) |
| 刺激 Stimulus | 到达系统的事件(请求、故障、修改意图等) |
| 工件 Artifact | 被刺激影响的系统部分(整个系统、进程、通信通道等) |
| 环境 Environment | 刺激发生时的系统条件(正常运行、过载、启动等) |
| 响应 Response | 系统对刺激作出的活动(处理、拒绝、恢复、通知等) |
| 响应度量 Measure | 响应的可量化指标(时间、概率、次数等) |
可操作性(Interoperability)场景示例:
刺激源: 外部合作伙伴系统 → 刺激: 通过标准协议发起数据交换请求
工件: 系统的集成接口 → 环境: 正常运行
响应: 接收并解析请求,按协议返回数据
度量: 成功集成时间 < 1 个月;协议兼容性达 100%
性能(Performance)场景示例:
刺激源: 用户/外部系统 → 刺激: 按一定频率到达的事件/请求
工件: 系统的某个进程 → 环境: 峰值负载(10000 用户在线)
响应: 处理事件并产生响应
度量: 95% 请求延迟 < 200ms;吞吐量 ≥ 1000 TPS
参考课件:
slides/2026SUG_SysArch2_quality attributes.pdf(质量属性场景);详见src/2017.md、src/review.md§3.3
B3. C&C 风格的性质,以 SOA 举例¶
组件-连接器风格(Component-and-Connector, C&C Style) 的性质:
- 关注运行时:C&C 风格描述系统在运行时如何被组织为一组具有行为和交互的元素。
- 元素:组件(Components,如客户端、服务器、过滤器)和连接器(Connectors,如调用、返回、事件、管道)。
- 组件是主要的计算单元和数据存储单元;连接器是组件间的交互机制(调用、事件订阅、消息等)。
- 关注点:组件如何交互、数据/控制流如何在系统内流动、并发与同步、运行时部署。
- 常见 C&C 风格:管道-过滤器、客户端-服务器、点对点、发布-订阅、面向服务(SOA)、分层(运行时视角)。
以 SOA(面向服务架构)举例:
- 组件:一组服务(Services),每个服务通过明确定义的契约对外暴露能力(粗粒度、可独立调用的业务功能)。
- 连接器:服务间的通信,通常通过 ESB(企业服务总线) 进行,使用重量级协议(SOAP / WS-*)。ESB 负责路由、协议转换、消息编排。
- 运行时性质体现:
- 服务在运行时被发现和绑定(通过注册中心)
- 服务间通过消息异步/同步协作
- 服务可被组合编排成新的业务流程(流程编排引擎)
- ESB 作为运行时中介,屏蔽服务位置与协议异构性
[Client] ──(SOAP)──▶ [ESB] ──(SOAP)──▶ [Service A]
├──(SOAP)──▶ [Service B]
└──(SOAP)──▶ [Service C]
↑
[服务注册中心]
参考课件:
slides/2026SUG_SysArch1_introduction.pdf(C&C 风格),slides/软件架构模式_update_2.pdf(SOA);详见src/2016.mdQ5、src/review.md§6.1
B4. ADD 3.0 过程¶
ADD(Attribute-Driven Design,属性驱动设计) 是一种递归的架构设计方法,以质量属性(ASR)为驱动,逐层分解系统。ADD 3.0 共 8 个步骤,形成一个迭代循环:
| 步骤 | 内容 |
|---|---|
| Step 1 | 确定驱动因素(Establish Drivers):确定设计目的、主要质量属性场景、约束、关心的问题 |
| Step 2 | 选择要分解的系统要素(Select Elements to Decompose):选择当前迭代要细化的元素 |
| Step 3 | 确定 ASR(Identify ASRs):针对选中的元素,识别其面临的主要 ASR |
| Step 4 | 选择设计决策(Choose Design Concepts):根据 ASR 选择合适的架构模式、战术、设计策略(含 7 类设计决策) |
| Step 5 | 实例化架构元素并分配职责(Instantiate Elements & Allocate Responsibilities):实例化所选模式,为每个元素分配职责(参考职责分配设计决策) |
| Step 6 | 定义接口(Define Interfaces):为实例化的元素定义对外接口 |
| Step 7 | 验证并解决不一致(Verify & Refine):验证设计是否满足 ASR,解决冲突,记录设计决策 |
| Step 8 | 迭代(Iterate):对未分解到所需粒度的元素,重复 Step 2-7,直到所有 ASR 满足 |
核心思想:分解 + 抽象 + 逐步求精 + 生成与测试 + 迭代-增量 + 复用元素(六种通用设计策略的全面体现)。
参考课件:
slides/Lecture 01 - Attributes Driven Design.pdf(ADD 3.0);详见src/review.md§7.2、src/2025.mdPart A Q3
B5. 微服务架构的主要特征¶
微服务的六大主要特征:
- 通过服务进行组件化(Componentization via Services):将系统拆分为独立可部署的服务组件,服务作为可独立替换、独立升级的单元。
- 围绕业务能力组织(Organized around Business Capabilities):服务按业务领域而非技术层划分,团队跨职能、端到端负责一个服务。
- 服务内高内聚、服务间低耦合(High Cohesion, Loose Coupling):遵循单一职责原则(SRP),每个服务聚焦一项业务能力。
- 去中心化(Decentralized):去中心化的数据管理(每个服务独立数据库)、去中心化的治理(技术栈自由选择)。
- 基础设施自动化(Infrastructure Automation):自动化测试、持续交付、自动化部署与监控预警(CI/CD)。
- 容错与演进式设计(Design for Failure / Evolutionary Design):服务设计考虑故障(熔断、降级),系统随业务持续演进。
其他补充特征:服务相对较小、可独立扩展、技术栈不受限、去 ESB 用轻量通信(REST/gRPC + 消息代理)、可大规模复杂程序持续交付。
参考课件:
slides/Microservices Patterns.pdf(微服务特征);详见src/2025.mdPart A Q9、src/review.md§8.1
B6. 工厂方法和抽象工厂属于什么类型,怎么体现 OCP?¶
模式类型:工厂方法和抽象工厂都属于创建型设计模式(Creational Patterns),关注对象的创建过程,将对象的创建与使用分离。
- 工厂方法(Factory Method):类创建型模式(基于继承)
- 抽象工厂(Abstract Factory):对象创建型模式(基于组合)
如何体现开闭原则(OCP)——"对扩展开放,对修改关闭":
工厂方法模式:
- 定义一个创建对象的接口(Creator),让子类决定实例化哪一个产品类。
- 体现 OCP:当需要新增一种产品时,只需新增一个
ConcreteCreator子类和对应的ConcreteProduct类,无需修改已有的Creator、Product接口和客户端代码。 - Creator 依赖抽象的 Product 接口(依赖倒转),对"新增产品类型"是开放的,对"修改已有代码"是关闭的。
抽象工厂模式:
- 提供一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类。
- 体现 OCP:当需要支持新的一系列产品时(如新的 GUI 主题、新的数据库产品族),只需新增一个
ConcreteFactory,无需修改已有的AbstractFactory接口、抽象产品接口和客户端代码。 - 客户端只依赖
AbstractFactory和AbstractProduct接口。
共同的核心机制:两种工厂模式都通过抽象化 + 多态 + 依赖倒转实现 OCP——客户端面向接口编程,新功能通过"添加新类"扩展,而非"修改已有类"。
参考课件:
slides/03工厂模式.pdf,slides/01面向对象设计原则.pdf(OCP);详见src/2022.mdQ2
B7. 一个 Notifier 和继承的 EmailNotifier,问是否满足 OOP 原则,解释¶
典型题干:父类 Notifier 有 notify(message) 方法(向用户发送通知),子类 EmailNotifier extends Notifier 重写该方法,但EmailNotifier 要求 message 不能为空且必须符合 email 格式,否则抛出异常。问此设计是否满足 OOP 原则。
结论:不满足,主要违反里氏代换原则(LSP)和开闭原则(OCP)。
违反里氏代换原则(LSP)——"子类必须能透明替换父类,程序行为不变":
- 父类
Notifier.notify(message)接受任意字符串(包括空串)。 - 子类
EmailNotifier.notify(message)增强了前置条件(要求非空、符合格式),对原本合法的输入(空串、普通文本)会抛异常。 - 凡是使用
Notifier类型并传入空串的客户端代码,在替换为EmailNotifier后会崩溃——子类没有"无条件增强父类行为",反而收紧了契约,违反 LSP。
违反开闭原则(OCP):
- 之所以出现这种继承,往往是因为想"复用 + 改行为"。但当父类契约被子类破坏后,调用方不得不写
if (notifier instanceof EmailNotifier)分支来判断,这违背了 OCP(对扩展开放、对修改关闭),也违背了依赖倒转(应当依赖抽象)。
更好的设计——优先使用组合而非继承(CRP):
// 反例(继承 + 破坏契约):
class Notifier { void notify(String msg) { /* 发送,接受任何字符串 */ } }
class EmailNotifier extends Notifier {
void notify(String msg) {
if (msg == null || !msg.contains("@")) throw ...; // 收紧前置条件,违反 LSP
// ...
}
}
// 正例(组合 + 抽象):把不同通知渠道作为策略
interface Channel { void send(String msg); }
class EmailChannel implements Channel { /* 校验 + 发邮件 */ }
class SmsChannel implements Channel { /* 发短信 */ }
class Notifier {
private List<Channel> channels;
void notify(String msg) { channels.forEach(c -> c.send(msg)); }
}
原则总结:父类与子类应为 IS-A 关系;若子类收紧契约/改变行为语义,说明两者不是真正的 IS-A,应改用组合(HAS-A)或策略模式。这同时符合 LSP + OCP + DIP + CRP。
参考课件:
slides/01面向对象设计原则.pdf(LSP / OCP / CRP);详见src/recite.md§一、src/2022.md§一(LSP)
B8. 命令模式的主要角色,其中调用者是否能去掉?¶
命令模式(Command Pattern)的主要角色(5 个):
| 角色 | 职责 |
|---|---|
| Command(抽象命令) | 声明执行命令的接口,通常含 execute() / undo() |
| ConcreteCommand(具体命令) | 实现 Command 接口,持有 Receiver 引用,把请求绑定到 Receiver |
| Invoker(调用者) | 持有 Command 引用,在某个时机调用 command.execute(),不关心接收者是谁 |
| Receiver(接收者) | 真正执行业务逻辑的对象 |
| Client(客户类) | 创建 ConcreteCommand 并装配 Invoker / Receiver |
[Client] ──creates──▶ [ConcreteCommand] ──holds──▶ [Receiver]
▲
│
[Invoker] ──triggers──execute()──┘
调用者(Invoker)能否去掉?
取决于使用命令模式的目的——命令模式的核心价值不是必须有 Invoker,而是"把请求封装成对象",从而支持参数化、排队、日志、撤销/重做、宏命令等。
- 若目的是"解耦发送者与接收者":可以没有显式 Invoker。客户端可以直接调用
command.execute(),Receiver 仍与发送方解耦。此时命令对象只是请求的"载体"。 - 若目的是"支持撤销/重做、排队、日志、宏命令":必须保留 Invoker——Invoker 负责维护命令历史、命令队列、调度执行时机。没有 Invoker,这些机制无处安放。例如:
- 菜单项/按钮(Invoker)记录命令历史以支持 Undo
- 调度器(Invoker)把命令排入队列延迟执行
- 持久化层(Invoker)把命令写入日志以支持崩溃恢复
结论:Invoker 不是命令模式结构上的强制角色(结构上可以由 Client 直接触发命令),但当需要撤销/排队/日志/宏命令等高级特性时,Invoker 是不可或缺的承载者。所以"能否去掉"要看命令模式使用的场景与动机。
参考课件:
slides/05状态与命令模式.pdf(命令模式角色);详见src/2021.mdQ4、src/2022.mdQ3
B9. Java 内置的 notifyObserver 为什么有两种重载版本?¶
Java java.util.Observable(已废弃)的 notifyObservers() 提供两个重载:
这对应观察者模式的两种通知模型:Pull Model(拉模型) 与 Push Model(推模型)。
| 版本 | 模型 | 机制 |
|---|---|---|
notifyObservers() |
Pull Model(拉模型) | Subject 仅通知"状态已改变",Observer 收到通知后调用 Observable 的 getter 自行拉取所需数据 |
notifyObservers(Object arg) |
Push Model(推模型) | Subject 把变化的数据作为 arg 推送给 Observer,Observer 直接使用 |
两种重载存在的原因(设计取舍):
- Pull 模型(无参):耦合低、灵活——Subject 无需知道每个 Observer 需要什么;但 Observer 要持有 Subject 引用并主动查询,代码较多。
- Push 模型(带参):Observer 实现简单,无需回查;但 Subject 可能推送 Observer 不需要的数据,且当 Subject 数据结构变化时需修改接口,耦合较高。
API 同时提供两个版本,让开发者根据数据量大小、Observer 需求差异、Subject 数据结构稳定性灵活选择。setChanged() 之后调用对应重载即可触发通知。
参考课件:
slides/06行为型模式.pdf(观察者模式 push/pull);详见src/2022.mdQ4
B10. 外观满足的原则,外观和代理的区别并举例¶
外观模式(Facade Pattern)满足的 OOP 原则:
- 迪米特法则(LoD,最少知识原则)——外观模式是 LoD 的典型体现。客户端只与外观"朋友"通信,不再需要与子系统内部的众多对象("陌生人")打交道。外观对象作为客户端与子系统之间的中介,降低了客户端的知识负担。
- 单一职责原则(SRP)——按 SRP 将复杂系统划分为若干子系统,并引入外观为每个子系统提供一个简单而单一的入口。
- 开闭原则(OCP)——外观松散了客户端与子系统的耦合,子系统内部变化不会影响客户端,对子系统修改关闭,对外观扩展开放。
- (间接支持)依赖倒转(DIP)——客户端依赖外观的稳定接口而非子系统内部细节。
外观模式 vs 代理模式(Proxy Pattern)的区别:
| 维度 | 外观模式(Facade) | 代理模式(Proxy) |
|---|---|---|
| 意图 | 为子系统提供统一简化入口,降低客户端与复杂子系统的耦合 | 为一个对象提供代理或占位,以控制对它的访问 |
| 结构关系 | 外观与子系统是 1 对多,外观聚合多个子系统对象 | 代理与被代理对象是 1 对 1,代理持有被代理对象引用,接口相同 |
| 接口 | 外观提供的是新的简化接口(不实现子系统接口) | 代理实现与被代理对象相同的接口,对外透明 |
| 典型用途 | 简化复杂子系统的访问 | 远程代理、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用 |
外观模式举例——家庭影院一键观影:
[Client: 遥控器] ──▶ [HomeTheaterFacade] ──▶ [Projector] + [Amplifier] + [DVDPlayer] + [Screen] + [Lights]
(一键 watchMovie() (子系统内部多个对象,客户端不直接接触)
依次协调 5 个子系统)
客户端调用 facade.watchMovie(),外观内部协调投影仪、功放、播放器、幕布、灯光。客户端不直接与 5 个子系统对象交互。
代理模式举例——远程代理(RPC Stub):
Stub 与 RealService 实现相同接口,客户端像调用本地对象一样调用远程对象,代理隐藏网络细节。
参考课件:
slides/09结构型模式.pdf(外观模式、代理模式);详见src/recite.md§二/§三
二、设计题¶
B11. 设计一个通知系统¶
题目要求:设计一个通知系统,通过多个通知渠道(Channel)通知利益相关者;不同渠道需满足运行时条件才能使用;系统要避免产生用不到的信息。
考点分析:综合考察策略模式 + 工厂模式 + 观察者模式 + 命令模式的协同。
设计要点:
- 多渠道 + 运行时条件 → 每个渠道是一个策略(Channel 接口),是否可用由运行时条件判定(策略选择)。
- 统一创建渠道 → 工厂模式创建 Channel 实例(创建型)。
- 利益相关者订阅通知 → 观察者模式:Stakeholder 订阅 NotificationService。
- 避免产生用不到的信息 → Pull 模型 + 懒求值 + 条件构造:通知内容只在"确实有可用渠道、确实有订阅者"时才生成;避免无谓的对象构造。
类图(ASCII):
┌──────────────────┐ subscribes ┌──────────────────────┐
│ Stakeholder │──────────────▶│ NotificationService │
│ (Observer) │ notify() │ (Subject / Facade) │
├──────────────────┤ ├──────────────────────┤
│ + update(ctx) │◀──────────────│ - channels:List<Channel> │
└──────────────────┘ push ctx │ - subscribers:Set │
│ + send(ctx) │
│ + register(o) │
└──────────┬───────────┘
│ uses (拉取所需信息)
▼
<<interface>> ┌─────────────────────┐
Channel │ NotificationContext │
├─────────────────── │ (Pull 模型数据源) │
│ + isAvailable(): │ + payload() : Msg │ ← 懒构造
│ + send(ctx): │ (按需才生成) │
└─────────┬───────── └─────────────────────┘
△ │
│ realizes │
┌────┴────┬─────────┬─────┴────┐
│EmailChan│SmsChannel│PushChannel│
│(条件:有 │(条件:有 │(条件:APP │
│ 邮箱地址)│ 手机号) │ 在线) │
└─────────┴──────────┴──────────┘
▲
│ creates
┌───────┴────────┐
│ ChannelFactory │ ← 根据 stakeholder 信息 + 运行时条件
│ + create(type)│ 选择并创建合适的 Channel
└────────────────┘
关键代码(Java 草案):
// 1. 渠道策略接口
interface Channel {
boolean isAvailable(Recipient r, RuntimeCondition cond); // 运行时条件判定
void send(NotificationContext ctx); // 发送(按需拉取内容)
}
class EmailChannel implements Channel {
public boolean isAvailable(Recipient r, RuntimeCondition c) {
return r.getEmail() != null && c.networkOk();
}
public void send(NotificationContext ctx) {
String body = ctx.payload(); // Pull:仅在真正要发时才生成内容
// ... 发邮件
}
}
// SmsChannel、PushChannel 类似
// 2. 工厂:根据类型与条件创建可用渠道
class ChannelFactory {
public List<Channel> createAvailable(Recipient r, RuntimeCondition c) {
List<Channel> all = List.of(new EmailChannel(), new SmsChannel(), new PushChannel());
return all.stream()
.filter(ch -> ch.isAvailable(r, c)) // 只保留运行时可用的渠道
.toList(); // 避免创建用不到的渠道实例
}
}
// 3. 观察者:利益相关者订阅
interface StakeholderObserver { void update(NotificationContext ctx); }
class NotificationService { // Subject + Facade
private final Set<StakeholderObserver> subs = new HashSet<>();
public void register(StakeholderObserver o) { subs.add(o); }
public void notify(Recipient r, Event e) {
List<Channel> channels = new ChannelFactory().createAvailable(r, RuntimeCondition.now());
if (channels.isEmpty() || subs.isEmpty()) return; // ★避免无用信息:无渠道/无订阅直接返回
// Pull 模型:Context 只在被需要时才构造真实内容
NotificationContext ctx = new NotificationContext(e, r); // 懒构造
subs.forEach(o -> o.update(ctx));
channels.forEach(ch -> ch.send(ctx));
}
}
"避免产生用不到的信息"的实现关键:
- Pull 模型:
NotificationContext只持有生成内容的"原料",真正的消息体payload()在 Channel 调用时才按需构造,无可用渠道则永不构造。 - 先过滤后构造:先
createAvailable过滤可用渠道与订阅者,过滤为空直接 early-return,避免生成无用的通知对象。 - 条件分发:运行时条件(网络、时间窗、订阅偏好)作为过滤谓词,而非先产生再丢弃。
模式收益:新增渠道只需新增 Channel 实现并由工厂注册(OCP);订阅者与渠道松耦合(LoD);内容按需生成(性能)。
参考课件:
slides/02策略模式.pdf、slides/03工厂模式.pdf、slides/06行为型模式.pdf(观察者)、slides/05状态与命令模式.pdf
B12. 外卖平台情景:为什么适合微服务?微服务拆分应用¶
题目要求:给定一个外卖平台情景,(1)说明为什么适合用微服务架构;(2)按微服务设计步骤进行拆分:操作(System Operations)、微服务(Services)、对应关系(API & Collaboration)。
(1)为什么外卖平台适合微服务架构?
外卖平台具有典型的"适合微服务"的特征:
| 特征 | 外卖平台的表现 | 单体架构的痛点 |
|---|---|---|
| 多业务领域 | 订单、支付、商户、配送、用户、评价、营销…… | 单体代码臃肿,团队冲突频繁 |
| 负载波动剧烈 | 午晚餐峰值 vs 凌晨低谷,差异巨大 | 整体扩容浪费,无法按子域独立伸缩 |
| 质量需求多样 | 订单要强一致;配送要高可用;搜索要低延迟 | 一个技术栈难以兼顾 |
| 多团队协作 | 商户、骑手、用户、运营团队各自演进 | 牵一发动全身,发布互相阻塞 |
| 业务持续演进 | 营销玩法、新配送方式不断迭代 | 演进式设计受阻于单体 |
| 可用性要求高 | 任一子域故障不应拖垮全局 | 单体故障全局不可用 |
微服务架构能按业务能力拆服务,让团队、代码、发布边界对齐,独立扩缩、独立故障隔离、技术栈自由——恰好契合外卖平台需求。
(2)微服务拆分步骤(来自 slides/Microservices Patterns.pdf):
Step 1: 识别系统操作 Step 2: 识别服务 Step 3: 定义服务 API 和协作
(System Operations) (Decompose by Capability) (Define APIs & Collaborations)
Step 1:识别系统操作(System Operations)——从用户故事/需求出发,提取系统的核心操作:
| 系统操作 | 描述 | 主要发起方 |
|---|---|---|
createOrder(customerId, restaurantId, items) |
用户下单 | 消费者 |
acceptOrder(orderId) |
商户接单 | 商户 |
prepareOrder(orderId) |
商户备餐完成 | 商户 |
assignCourier(orderId) |
系统派骑手 | 调度系统 |
pickupOrder(orderId, courierId) |
骑手取餐 | 骑手 |
deliverOrder(orderId) |
骑手送达 | 骑手 |
pay(orderId, method) |
支付订单 | 消费者 |
rateOrder(orderId, score) |
评价订单 | 消费者 |
Step 2:识别服务(按业务能力拆分 Decompose by Business Capability):
| 微服务 | 业务能力 | 独立数据 |
|---|---|---|
| Order Service(订单服务) | 订单生命周期管理 | OrderDB |
| Restaurant Service(商户服务) | 商户、菜单、库存 | RestaurantDB |
| Kitchen Service(厨房/备餐服务) | 备餐 ticket 管理 | KitchenDB |
| Delivery Service(配送服务) | 骑手调度、配送状态 | DeliveryDB |
| Payment Service(支付服务) | 支付、对账 | PaymentDB |
| Consumer Service(消费者服务) | 用户账号、地址 | ConsumerDB |
| Courier Service(骑手服务) | 骑手账号、位置 | CourierDB |
| Notification Service(通知服务) | 短信/推送 | — |
Step 3:定义服务 API 与协作关系(对应关系):
消费者 ──createOrder──▶ Order Service ──verifyOrder──▶ Restaurant Service
│ │
│ ▼
│ Kitchen Service
│ │
▼ │
Payment Service ◀───pay─── Order Service
│
▼
Delivery Service ──assignCourier──▶ Courier Service
│
▼
Notification Service ──▶ (各 Stakeholder)
| 主操作 | 服务协作链 |
|---|---|
createOrder |
Order Service → 调用 Restaurant.verifyOrder 验证菜单/库存 → 创建订单 |
acceptOrder |
Restaurant Service → 调用 Kitchen.createTicket 通知备餐 → 回写 Order |
pay |
Order Service → 调用 Payment.charge → 成功后触发 Notification |
deliverOrder |
Delivery Service → 调用 Courier.assign → 取餐/送达 → 触发 Notification |
拆分原则落地:
- 高内聚:每个服务聚焦单一业务能力(SRP),Order 只管订单状态机,Payment 只管支付。
- 松耦合:服务间通过 REST/gRPC API 或消息(订单事件 → 消息队列 → 各订阅者)协作,不共享数据库。
- 独立可部署:每个服务有独立 DB、独立 CI/CD、独立扩缩——配送服务可在午高峰独立扩容。
- 按业务能力而非技术层拆:不拆成"Web 层 / Service 层 / DAO 层",而是按订单、商户、配送等业务子域。
可观测性与可靠性补充:引入 API Gateway(统一入口)、Circuit Breaker(防止配送故障蔓延到订单)、Service Registry(服务发现)、分布式追踪(跨服务排障)。
参考课件:
slides/Microservices Patterns.pdf(微服务拆分、通信、部署模式);详见src/review.md§8、§9、§10,src/2025.mdPart A Q8/Q9
Part B 综合:本部分为 2025 真实考卷学长回忆整理,设计模式相关答案已与
slides/01–09课件原文核对,架构相关答案参考src/下各年解答。