跳转至

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 的区别?

  1. Design 包含 Architecture,Architecture 包含 Structure
  2. 所有架构都是设计,但不是所有设计都是架构。体系结构是软件设计的一个部分
  3. 结构(Structure) 是静态的、逻辑的,关于系统如何构成
  4. 架构(Architecture) 除包含结构,还包含组件之间的相关关系结构,并定义一些动态行为
  5. 程序或计算系统的软件架构是系统的一个或多个结构,包括软件元素、这些元素的外部可见属性以及它们之间的关系

参考课件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 平均维修时间——系统从故障中恢复所需的平均时间

计算公式

\[\text{Availability} = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}}\]

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.pdfslides/软件架构模式_update_2.pdf


9. 微服务架构的六大特性 (6分)

  1. 通过服务组件化(Componentization via Services):将系统拆分为独立的服务组件,每个服务可独立部署
  2. 围绕业务能力组织(Organized around Business Capabilities):而非聚焦在技术层面,服务按业务领域划分
  3. 服务内部高内聚,服务间低耦合(High Cohesion, Loose Coupling):遵循单一职责原则
  4. 去中心化(Decentralized):去中心化的数据管理和治理
  5. 基础设施自动化(Infrastructure Automation):自动化测试、部署、监控
  6. 服务高可用性设计、演进式设计(Evolutionary Design):容错设计,允许系统持续演进

其他特性: - 每个服务相对较小并容易维护 - 服务可以独立扩展 - 技术栈不受限 - 大型复杂程序可以持续部署和交付 - 高可伸缩

参考课件slides/Microservices Patterns.pdf


10. 企业架构的核心是什么?它们在企业数字化转型中发挥什么作用? (6分)

企业架构的核心

企业架构(Enterprise Architecture, EA)的核心包括:

  1. 业务架构(Business Architecture):描述业务战略、组织结构和业务流程
  2. 数据架构(Data Architecture):描述企业数据资产的管理和使用
  3. 应用架构(Application Architecture):描述应用系统的结构和交互
  4. 技术架构(Technology Architecture):描述支撑应用和数据的技术基础设施

在数字化转型中的作用

  1. 战略对齐:确保 IT 投资和技术决策与企业的业务战略保持一致
  2. 标准化与整合:建立技术标准和规范,消除信息孤岛,促进系统间的互操作性
  3. 变更管理:为数字化转型提供路线图和过渡规划,管理从现状到目标状态的演进
  4. 风险管控:识别和管理技术债务、安全风险和合规问题
  5. 决策支持:为管理层提供全面的 IT 全景视图,支撑投资和建设决策
  6. 创新能力:通过架构框架识别新技术的引入机会,支持业务模式创新

参考课件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) OrderIdMoney 等值对象替代 StringBigDecimal
命名不一致 统一语言(Ubiquitous Language) 统一业务术语命名

参考课件exams/软件系统设计-复习-EagleBear.pdf(DDD 参考),slides/Lecture 01 - Attributes Driven Design.pdf


综合参考exams/软件系统设计-复习-EagleBear.pdf(综合复习资料)


Part B(2025 真实考卷 · 学长回忆版)

来源:exams/2025回忆.md(荣耀笔记回忆) 注:以下为考后回忆整理,题干表述可能与真实考卷有出入,但考点覆盖可靠。

一、简答题

B1. 什么需求影响架构,怎么得到?

影响架构的需求类型(三类):

  1. 功能需求(Functional Requirements):说明系统必须做什么、如何为利益相关者提供价值。功能需求通常决定架构的元素(components/connectors),但一般不唯一决定架构——同一组功能可由多种架构实现。
  2. 质量需求(Quality Attribute Requirements):系统的整体理想属性,如性能、可用性、安全性、可修改性、可测试性等。质量需求是决定架构形态的主要因素,不同的质量需求会导致截然不同的架构。
  3. 约束(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.md Q3、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.mdsrc/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.md Q5、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.md Part A Q3


B5. 微服务架构的主要特征

微服务的六大主要特征

  1. 通过服务进行组件化(Componentization via Services):将系统拆分为独立可部署的服务组件,服务作为可独立替换、独立升级的单元。
  2. 围绕业务能力组织(Organized around Business Capabilities):服务按业务领域而非技术层划分,团队跨职能、端到端负责一个服务。
  3. 服务内高内聚、服务间低耦合(High Cohesion, Loose Coupling):遵循单一职责原则(SRP),每个服务聚焦一项业务能力。
  4. 去中心化(Decentralized):去中心化的数据管理(每个服务独立数据库)、去中心化的治理(技术栈自由选择)。
  5. 基础设施自动化(Infrastructure Automation):自动化测试、持续交付、自动化部署与监控预警(CI/CD)。
  6. 容错与演进式设计(Design for Failure / Evolutionary Design):服务设计考虑故障(熔断、降级),系统随业务持续演进。

其他补充特征:服务相对较小、可独立扩展、技术栈不受限、去 ESB 用轻量通信(REST/gRPC + 消息代理)、可大规模复杂程序持续交付。

参考课件slides/Microservices Patterns.pdf(微服务特征);详见 src/2025.md Part A Q9、src/review.md §8.1


B6. 工厂方法和抽象工厂属于什么类型,怎么体现 OCP?

模式类型:工厂方法和抽象工厂都属于创建型设计模式(Creational Patterns),关注对象的创建过程,将对象的创建与使用分离。

  • 工厂方法(Factory Method):类创建型模式(基于继承)
  • 抽象工厂(Abstract Factory):对象创建型模式(基于组合)

如何体现开闭原则(OCP)——"对扩展开放,对修改关闭":

工厂方法模式

  • 定义一个创建对象的接口(Creator),让子类决定实例化哪一个产品类
  • 体现 OCP:当需要新增一种产品时,只需新增一个 ConcreteCreator 子类和对应的 ConcreteProduct 类,无需修改已有的 CreatorProduct 接口和客户端代码。
  • Creator 依赖抽象的 Product 接口(依赖倒转),对"新增产品类型"是开放的,对"修改已有代码"是关闭的。

抽象工厂模式

  • 提供一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类。
  • 体现 OCP:当需要支持新的一系列产品时(如新的 GUI 主题、新的数据库产品族),只需新增一个 ConcreteFactory无需修改已有的 AbstractFactory 接口、抽象产品接口和客户端代码。
  • 客户端只依赖 AbstractFactoryAbstractProduct 接口。

共同的核心机制:两种工厂模式都通过抽象化 + 多态 + 依赖倒转实现 OCP——客户端面向接口编程,新功能通过"添加新类"扩展,而非"修改已有类"。

参考课件slides/03工厂模式.pdfslides/01面向对象设计原则.pdf(OCP);详见 src/2022.md Q2


B7. 一个 Notifier 和继承的 EmailNotifier,问是否满足 OOP 原则,解释

典型题干:父类 Notifiernotify(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.md Q4、src/2022.md Q3


B9. Java 内置的 notifyObserver 为什么有两种重载版本?

Java java.util.Observable(已废弃)的 notifyObservers() 提供两个重载:

public void notifyObservers();               // 无参版本
public void notifyObservers(Object arg);     // 带参版本

这对应观察者模式的两种通知模型: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.md Q4


B10. 外观满足的原则,外观和代理的区别并举例

外观模式(Facade Pattern)满足的 OOP 原则

  1. 迪米特法则(LoD,最少知识原则)——外观模式是 LoD 的典型体现。客户端只与外观"朋友"通信,不再需要与子系统内部的众多对象("陌生人")打交道。外观对象作为客户端与子系统之间的中介,降低了客户端的知识负担。
  2. 单一职责原则(SRP)——按 SRP 将复杂系统划分为若干子系统,并引入外观为每个子系统提供一个简单而单一的入口。
  3. 开闭原则(OCP)——外观松散了客户端与子系统的耦合,子系统内部变化不会影响客户端,对子系统修改关闭,对外观扩展开放。
  4. (间接支持)依赖倒转(DIP)——客户端依赖外观的稳定接口而非子系统内部细节。

外观模式 vs 代理模式(Proxy Pattern)的区别

维度 外观模式(Facade) 代理模式(Proxy)
意图 为子系统提供统一简化入口,降低客户端与复杂子系统的耦合 为一个对象提供代理或占位,以控制对它的访问
结构关系 外观与子系统是 1 对多,外观聚合多个子系统对象 代理与被代理对象是 1 对 1,代理持有被代理对象引用,接口相同
接口 外观提供的是新的简化接口(不实现子系统接口) 代理实现与被代理对象相同的接口,对外透明
典型用途 简化复杂子系统的访问 远程代理、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用

外观模式举例——家庭影院一键观影:

[Client: 遥控器] ──▶ [HomeTheaterFacade]  ──▶ [Projector] + [Amplifier] + [DVDPlayer] + [Screen] + [Lights]
                    (一键 watchMovie()        (子系统内部多个对象,客户端不直接接触)
                     依次协调 5 个子系统)

客户端调用 facade.watchMovie(),外观内部协调投影仪、功放、播放器、幕布、灯光。客户端不直接与 5 个子系统对象交互。

代理模式举例——远程代理(RPC Stub):

[Client] ──▶ [Stub 代理]  ──(网络)──▶ [RealService(远程对象)]
            (与 RealService 接口相同,
             负责序列化、网络通信、结果返回)

StubRealService 实现相同接口,客户端像调用本地对象一样调用远程对象,代理隐藏网络细节。

参考课件slides/09结构型模式.pdf(外观模式、代理模式);详见 src/recite.md §二/§三


二、设计题

B11. 设计一个通知系统

题目要求:设计一个通知系统,通过多个通知渠道(Channel)通知利益相关者;不同渠道需满足运行时条件才能使用;系统要避免产生用不到的信息

考点分析:综合考察策略模式 + 工厂模式 + 观察者模式 + 命令模式的协同。

设计要点

  1. 多渠道 + 运行时条件 → 每个渠道是一个策略(Channel 接口),是否可用由运行时条件判定(策略选择)。
  2. 统一创建渠道 → 工厂模式创建 Channel 实例(创建型)。
  3. 利益相关者订阅通知 → 观察者模式:Stakeholder 订阅 NotificationService。
  4. 避免产生用不到的信息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策略模式.pdfslides/03工厂模式.pdfslides/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.md Part A Q8/Q9


Part B 综合:本部分为 2025 真实考卷学长回忆整理,设计模式相关答案已与 slides/01–09 课件原文核对,架构相关答案参考 src/ 下各年解答。