2022 年软件系统设计考题解答¶
来源:
exams/软统2022试卷.pdf「软件系统设计(A卷)」2022年6月 教师:张贺、潘敏学 | 闭卷考试 注:本试卷通过 OCR 提取并人工校对。
一、简答题(共60分,每题6分)¶
1. Please explain the Liskov Substitution Principle and how it contributes to the Open-Closed Principle. (6 points)¶
里氏代换原则(LSP)¶
里氏代换原则(Liskov Substitution Principle, LSP): - 所有引用基类(父类)的地方必须能透明地使用其子类的对象 - 即:如果对每一个类型为 S 的对象 o1,都有类型为 T 的对象 o2,使得以 T 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有变化,那么类型 S 是类型 T 的子类型
LSP 如何促进开闭原则(OCP)¶
LSP 是实现 OCP 的重要方式之一:
- OCP 要求:软件实体应当对扩展开放,对修改关闭
- LSP 是实现 OCP 的基础:因为使用基类对象的地方都可以使用子类对象,所以在程序中可以尽量使用基类类型来定义对象,而在运行时再确定其子类类型,用子类对象来替换父类对象
- 通过 LSP 实现扩展:当需要扩展功能时,只需添加新的子类(对扩展开放),而无需修改使用基类的客户端代码(对修改关闭)
示例:在策略模式中,Duck 类通过 FlyBehavior 接口引用具体策略,任何新的飞行行为(如 RocketFly)只需实现 FlyBehavior 接口即可,无需修改 Duck 类——这同时遵循了 LSP(子类可替换父类)和 OCP(对扩展开放,对修改关闭)。
参考课件:
slides/01面向对象设计原则.pdf(LSP 和 OCP),slides/02策略模式.pdf
2. Please explain how the Factory Method Pattern and Abstract Factory Pattern adhere to the Open-Closed Principle. (6 points)¶
工厂方法模式(Factory Method Pattern)¶
工厂方法模式通过以下方式遵循 OCP:
- 定义一个用于创建对象的接口(Creator),让子类决定实例化哪一个类
- 当需要新增一种产品时,只需添加一个新的 ConcreteCreator 子类和一个新的 ConcreteProduct 类,无需修改已有的 Creator 和 Product 代码
- Creator 依赖于抽象的 Product 接口(依赖倒转),对具体产品类型的变化是封闭的,但对新增产品类型是开放的
抽象工厂模式(Abstract Factory Pattern)¶
抽象工厂模式通过以下方式遵循 OCP:
- 提供一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类
- 当需要支持新的一系列产品时(如新的 GUI 主题),只需添加一个新的 ConcreteFactory,无需修改已有的 AbstractFactory 接口和客户端代码
- 客户端只依赖于 AbstractFactory 和 AbstractProduct 接口(依赖倒转)
共同的核心机制¶
两种工厂模式都通过抽象化 + 多态来实现 OCP: - 客户端面向抽象(接口/抽象类)编程 - 新功能通过添加新类实现(扩展),而非修改已有类(修改)
参考课件:
slides/03工厂模式.pdf(工厂方法模式和抽象工厂模式),slides/01面向对象设计原则.pdf(OCP)
3. What is the benefit of decoupling the Receiver from the Invoker in the Command Pattern? (6 points)¶
解耦 Receiver 和 Invoker 的好处¶
在命令模式中,Invoker(调用者)不直接知道 Receiver(接收者),而是通过 Command 对象间接调用:
- 易于扩展新命令:添加新命令只需实现 Command 接口,无需修改 Invoker 或 Receiver
- 支持撤销/重做(Undo/Redo):Command 对象可以存储操作的历史状态,支持撤销和恢复
- 支持命令队列和日志:命令可以被排队、记录日志,在系统崩溃后可重新执行
- 支持宏命令(组合命令):可以将多个 Command 组合成一个复合命令
- 降低耦合度:Invoker 只依赖 Command 接口,不依赖具体的 Receiver,可以在运行时动态替换
- 支持事务:可以将一系列命令作为一个原子操作
结构示意¶
参考课件:
slides/05状态与命令模式.pdf(命令模式)
4. Observer Pattern: push model vs pull model. Trade-offs? (6 points)¶
Push Model(推送模型)¶
- 方式:Subject 将所有数据作为参数推送给 Observer
- 优点:Observer 实现简单,不需要再向 Subject 查询数据
- 缺点:
- Subject 可能发送 Observer 不需要的数据
- 当 Subject 数据字段增加时,需要修改 Observer 接口
- 耦合度较高——Subject 需要了解 Observer 需要什么数据
Pull Model(拉取模型)¶
- 方式:Subject 仅通知 Observer 状态已改变(最小通知),Observer 自己向 Subject 拉取所需数据
- 优点:
- Observer 可以按需获取数据,灵活性更高
- 当 Subject 添加新字段时,不需要修改已有的 Observer
- 耦合度较低
- 缺点:
- Observer 需要持有 Subject 引用
- Observer 需要更多的代码来实现数据拉取
选择指导¶
| 场景 | 推荐模型 |
|---|---|
| Subject 数据量小,所有 Observer 都需要全部数据 | Push |
| 不同 Observer 需要不同数据子集 | Pull |
| Subject 数据结构经常变化 | Pull |
| 需要最小化 Observer 复杂度 | Push |
参考课件:
slides/06行为型模式.pdf(观察者模式)
5. Difference between Creational Patterns and Structural Patterns? What categories do Prototype and Flyweight fall into? Explain why. (6 points)¶
创建型模式 vs 结构型模式¶
| 维度 | 创建型模式(Creational) | 结构型模式(Structural) |
|---|---|---|
| 目的 | 主要用于创建对象 | 主要用于处理类或对象的组合 |
| 关注点 | 如何创建对象、隐藏创建逻辑 | 如何组合类和对象形成更大的结构 |
| 代表模式 | 工厂方法、抽象工厂、建造者、原型、单例 | 适配器、桥接、组合、装饰、外观、享元、代理 |
Prototype(原型模式)→ 创建型模式¶
- 分类:创建型模式、对象模式
- 原因:原型模式的核心目的是创建新对象——通过复制(克隆)原型实例来创建新对象,而不是通过 new。它关注的是对象的创建方式(如何创建),而非对象的结构组织。属于对象模式是因为处理的是对象之间的克隆关系
Flyweight(享元模式)→ 结构型模式¶
- 分类:结构型模式、对象模式
- 原因:享元模式的核心目的是组织对象的结构——通过共享技术有效地支持大量细粒度对象,减少内存占用。它关注的是如何组织已有对象(结构组织),而非如何创建新对象。属于对象模式是因为享元工厂管理的是对象实例
参考课件:
slides/00概述.pdf(设计模式分类表),slides/04创建型模式.pdf(原型模式),slides/09结构型模式.pdf(享元模式)
6. Describe the difference and relationship between software requirements, quality attributes, and ASR. (6 points)¶
定义与关系¶
软件需求(Software Requirements)
├── 功能性需求:系统必须做什么,说明系统如何为利益相关者提供价值
└── 非功能性需求(质量需求)
├── 质量属性(QA):系统在功能需求之上提供的整个系统的合乎需求的特性
└── 约束:技术限制、商业限制、法律法规等
└── ASR(架构攸关需求):对架构设计决策产生深远影响的需求
区别与联系¶
| 概念 | 定位 |
|---|---|
| 软件需求(Software Requirements) | 最广泛的概念,包含功能性和非功能性需求 |
| 质量属性(Quality Attributes) | 非功能性需求的一种反映,是系统在功能之上的理想特征(性能、可用性、安全性等) |
| ASR | 质量属性要求越困难、越重要,就越有可能成为 ASR。如果需求影响关键架构设计决策,它就是一个 ASR |
关键关系¶
非功能性需求/体系结构需求是质量属性的替代术语。质量属性由业务目标决定。QA → 重要的 QA → 成为 ASR。
参考课件:
slides/2026SUG_SysArch2_quality attributes.pdf,slides/2026SUG_SysArch1_introduction.pdf
7. Define 'Availability' as a quality attribute. What do MTBF and MTTR stand for? How to calculate the availability (e.g., SLA)? (6 points)¶
可用性定义¶
可用性(Availability) 是系统在指定的时间间隔内,能够在要求的范围内提供指定服务的能力。可将可用性计算为在指定时间间隔内系统能提供指定服务的概率。
MTBF 和 MTTR¶
| 缩写 | 全称 | 含义 |
|---|---|---|
| MTBF | Mean Time Between Failures(平均无故障时间) | 系统正常运行的平均时间长度 |
| MTTR | Mean Time To Repair(平均维修时间) | 系统从故障中恢复所需的平均时间 |
可用性计算公式¶
示例:如果一个系统的 MTBF = 99 小时,MTTR = 1 小时,则:
这对应 SLA 中的"两个9"(99% 可用性)。常见的 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(可用性质量属性)
8. What are the generic design strategies applied in designing software? Give a concise working example with software architecture for these strategies. (6 points)¶
六种通用设计策略:
| 策略 | 示例 |
|---|---|
| 分解(Decomposition) | 将整个系统分解为前端展示层、业务逻辑层、数据访问层(三层架构) |
| 抽象(Abstraction) | 将系统抽象为组件(Component)和连接器(Connector),屏蔽实现细节 |
| 逐步求精/分而治之 | 对每个模块分别设计:先定义接口,再逐步细化实现 |
| 生成与测试(Generate and Test) | 设计评审后针对关键路径生成测试用例进行验证 |
| 迭代-增量细化 | 使用 ADD 方法多次迭代直到满足所有 ASR |
| 复用元素(Reuse of Elements) | 重用现有架构模式如 MVC、分层架构、消息队列等 |
参考课件:
slides/Lecture 01 - Attributes Driven Design.pdf
9. Why should a software architecture be documented using different views? Give the names and purposes of four example views. (6 points)¶
为什么需要不同视图¶
- 不同视图支持不同的目标和用户,突出不同的系统元素和关系
- 不同视图将不同质量属性暴露出不同的程度
- 单一视图无法描述软件架构的全部内容
- 分离关注点(Separation of Concerns),管理架构复杂性
四种视图及其目的¶
| 视图 | 目的 |
|---|---|
| 逻辑视图(Logical View) | 描述对架构重要的元素及其关系(功能需求),面向最终用户 |
| 过程视图(Process View) | 描述元素间的并发和交互(进程、线程、同步),面向系统集成者 |
| 开发视图(Development View) | 描述软件模块的内部组织联系,面向程序员和开发团队 |
| 物理视图(Physical View) | 描述主要过程和组件如何映射到硬件,面向系统工程师 |
参考课件:
slides/2026SUG_SysArch1_introduction.pdf(架构视图)
10. What are the risks, sensitivity points, and trade-off points? Give an example for each. (6 points)¶
| 概念 | 定义 | 示例 |
|---|---|---|
| 风险(Risk) | 可能对所需质量属性产生负面影响的架构决策 | 使用分层模式可能带来性能损耗,因为每层调用会增加额外开销 |
| 敏感点(Sensitivity Point) | 特定质量属性对其敏感的架构决策——微小变化导致质量属性显著变化 | 在性能敏感的系统中,决定在某处使用缓存中间件——缓存命中率的微小变化会显著影响系统响应时间 |
| 权衡点(Trade-off Point) | 影响多个质量属性的架构决策,改善一个可能损害另一个 | 使用分层模式带来性能损耗(性能↓),但同时解耦增加系统可修改性(可修改性↑) |
参考课件:
slides/Lecture 01 - Attributes Driven Design.pdf(ATAM),slides/Lecture 02 - Attributes Driven Design - Case Study.pdf
二、设计题(共40分)¶
1. Multi-instance Pattern 设计 (10 points)¶
要求:设计 Multi-instance 模式,确保系统中某个类的对象数量不超过给定的常数 n。
设计思路¶
Multi-instance 模式是单例模式的推广——允许创建固定数量(n 个)的实例。核心机制: - 使用对象池(Object Pool) 管理有限数量的实例 - 通过静态计数器跟踪已创建的实例数 - 提供静态工厂方法获取实例
类图¶
┌──────────────────────────────┐
│ MultiInstance │
├──────────────────────────────┤
│ - static instances: │
│ List<MultiInstance> │
│ - static MAX_INSTANCES: int │
│ - static count: int │
│ - id: int │
├──────────────────────────────┤
│ - MultiInstance() // private │
│ + static getInstance(): │
│ MultiInstance │
│ + static releaseInstance( │
│ instance): void │
│ + getId(): int │
└──────────────────────────────┘
代码实现¶
import java.util.ArrayList;
import java.util.List;
public class MultiInstance {
private static final int MAX_INSTANCES = 5; // n
private static List<MultiInstance> instances = new ArrayList<>();
private static List<Boolean> available = new ArrayList<>();
private static int currentCount = 0;
private int id;
// 私有构造函数
private MultiInstance() {
this.id = currentCount;
}
// 获取一个可用实例
public static synchronized MultiInstance getInstance() {
// 查找是否有已释放的可用实例
for (int i = 0; i < instances.size(); i++) {
if (available.get(i)) {
available.set(i, false);
return instances.get(i);
}
}
// 如果还有配额,创建新实例
if (currentCount < MAX_INSTANCES) {
MultiInstance instance = new MultiInstance();
instances.add(instance);
available.add(false);
currentCount++;
return instance;
}
// 达到上限,返回 null 或抛出异常
throw new IllegalStateException(
"Maximum instances (" + MAX_INSTANCES + ") exceeded");
}
// 释放实例,使其可被重用
public static synchronized void releaseInstance(MultiInstance instance) {
int index = instances.indexOf(instance);
if (index >= 0) {
available.set(index, true);
}
}
public int getId() { return id; }
}
参考课件:
slides/04创建型模式.pdf(单例模式),slides/补充-单例模式.pdf
2. OA 通知系统设计 (10 points)¶
要求:设计 OA 系统,存储公司部门和人,形成树状结构。发通知时可以通知整个部门的所有人或指定个别人员。
设计模式:组合模式(Composite Pattern)¶
选择理由:部门和人员形成部分-整体层次(树状结构),客户端需要统一对待单个对象和组合对象。
类图¶
┌──────────────────────────┐
│ <<interface>> │
│ Notifiable │
├──────────────────────────┤
│ + sendNotice(msg: String) │
│ + add(child: Notifiable) │
│ + remove(child: │
│ Notifiable) │
│ + getName(): String │
└──────┬───────────────────┘
△
│
┌────┴──────────────┐
│ │
┌─┴──────────────┐ ┌─┴──────────────┐
│ Department │ │ Person │
│ (部门-组合节点)│ │ (人员-叶节点) │
├────────────────┤ ├────────────────┤
│ - name: String │ │ - name: String │
│ - children: │ │ - title: String │
│ List<Notifiable>│ ├────────────────┤
├────────────────┤ │+ sendNotice() │
│+ sendNotice(): │ │+ add() - │
│ 递归发送给所有子 │ │ Unsupported │
│ 节点 │ │+ remove() - │
│+ add() │ │ Unsupported │
│+ remove() │ │+ getName() │
│+ getName() │ └────────────────┘
└────────────────┘
┌──────────────────────────────────────┐
│ OASystem │
├──────────────────────────────────────┤
│ - root: Notifiable │
│ - targetList: List<Notifiable> │
├──────────────────────────────────────┤
│ + issueNotice(targets, msg): void │
│ + displayNotices(person): void │
└──────────────────────────────────────┘
代码实现¶
// 接口
interface Notifiable {
void sendNotice(String message);
default void add(Notifiable child) {
throw new UnsupportedOperationException();
}
default void remove(Notifiable child) {
throw new UnsupportedOperationException();
}
String getName();
}
// 部门(组合节点)
class Department implements Notifiable {
private String name;
private List<Notifiable> children = new ArrayList<>();
public Department(String name) { this.name = name; }
public void sendNotice(String message) {
System.out.println("[部门] " + name + " 收到通知: " + message);
for (Notifiable child : children) {
child.sendNotice(message);
}
}
public void add(Notifiable child) { children.add(child); }
public void remove(Notifiable child) { children.remove(child); }
public String getName() { return name; }
}
// 人员(叶节点)
class Person implements Notifiable {
private String name;
private String title;
private List<String> notices = new ArrayList<>();
public Person(String name, String title) {
this.name = name;
this.title = title;
}
public void sendNotice(String message) {
notices.add(message);
System.out.println("[" + title + "] " + name + " 收到通知: " + message);
}
public String getName() { return name; }
public List<String> getNotices() { return notices; }
}
// OA 系统
class OASystem {
private Department root;
public OASystem() {
root = new Department("公司总部");
}
public void issueNotice(List<Notifiable> targets, String message) {
for (Notifiable target : targets) {
target.sendNotice(message);
}
}
public Department getRoot() { return root; }
}
// 使用示例
// Department tech = new Department("技术部");
// Person alice = new Person("Alice", "工程师");
// tech.add(alice);
// oa.issueNotice(Arrays.asList(tech, bob), "系统升级通知");
参考课件:
slides/07适配器与组合.pdf(组合模式)
3. Distributed Cache Updates 设计题 (20 points)¶
场景:在线客服系统,N 个服务器集群,每个服务器有缓存。要求: - REQ1:缓存变更提交到数据库 - REQ2:所有其他缓存状态必须失效并从数据库重新加载
(a) 识别额外需要的组件 (5 points)
额外组件:Invalidation Coordinator(失效协调器)
| 组件名称 | 主要功能 |
|---|---|
| InvalidationCoordinator(失效协调器) | ① 接收缓存变更通知 ② 向所有其他缓存广播失效消息 ③ 管理分布式缓存的一致状态 |
| 替代方案:可用 Message Broker(消息代理) 或 Event Bus(事件总线) | 使用发布-订阅模式,当一个缓存变更时发布事件,其他缓存订阅失效事件 |
(b) 将 Observer 模式角色映射到组件 (5 points)
| Observer 模式角色 | 映射到的组件 |
|---|---|
| Subject(主题/被观察者) | 被修改的 Cache(缓存)+ InvalidationCoordinator |
| Observer(观察者) | 所有其他 Server 的 Cache |
| ConcreteSubject | 发生变更的具体 Cache 实例 |
| ConcreteObserver | 其他需要失效并重新加载的 Cache 实例 |
| notifyObserver() | InvalidationCoordinator 广播失效消息 |
| update() | 各个 Cache 的 invalidate() + reload() |
(c) 序列图 (5 points)
Client Server1 Cache1 Invalidation Cache2 Database
│ │ │ Coordinator │ │
│ 请求修改 │ │ │ │ │
│─────────▶│ │ │ │ │
│ │ 更新数据 │ │ │ │
│ │─────────▶│ │ │ │
│ │ │ 提交变更 │ │ │
│ │ │─────────────────────────│─────────▶│
│ │ │ │ │ write │
│ │ │◀─────────────────────────│──────────│
│ │ │ notify(invalidation) │ │
│ │ │───────────▶│ │ │
│ │ │ │ broadcast │ │
│ │ │ │ invalidate │ │
│ │ │ │───────────▶│ │
│ │ │ │ │ invalidate│
│ │ │ │ │ reload │
│ │ │ │ │─────────▶│
│ │ │ │ │◀─────────│
│◀─────────│──────────│ │ │ │
│ 返回结果 │ │ │ │ │
(d) Connectors/Web Services 解决异构缓存访问 (5 points)
问题:不同服务器使用不同的协议(Java RMI、HTTP JSP、SOAP),缓存接口不一致。
解决方案:使用 Connector 模式 + Web Service 封装
| 方案 | 描述 |
|---|---|
| Connector 模式 | 为每种协议创建对应的 Connector 适配器(如 RmiConnector、HttpConnector、SoapConnector),每个 Connector 实现统一的 CacheConnector 接口,封装协议差异 |
| Web Service 方案 | 将每个 Cache 封装为标准的 RESTful Web Service,使用统一的 HTTP + JSON 接口。所有服务器通过 HTTP 协议调用缓存操作(GET /cache/{key}、POST /cache/invalidate),屏蔽底层实现差异 |
Connector 模式设计:
// 统一接口
interface CacheConnector {
void invalidate(String key);
Object load(String key);
void update(String key, Object value);
}
// 适配不同协议
class RmiCacheConnector implements CacheConnector { /* Java RMI */ }
class HttpCacheConnector implements CacheConnector { /* HTTP/JSP */ }
class SoapCacheConnector implements CacheConnector { /* SOAP */ }
// InvalidationCoordinator 使用统一接口
class InvalidationCoordinator {
private Map<String, CacheConnector> connectors;
public void broadcastInvalidation(String key) {
for (CacheConnector conn : connectors.values()) {
conn.invalidate(key);
}
}
}
关键思想:通过统一的接口抽象(Connector 模式或 Web Service 封装),使得 InvalidationCoordinator 无需关心每个缓存的底层协议差异。
参考课件:
slides/软件架构模式_update_2.pdf(Broker/Connector),slides/06行为型模式.pdf(观察者模式),slides/Lecture 01 - Attributes Driven Design.pdf(架构战术)
综合参考:
exams/软件系统设计-复习-EagleBear.pdf(综合复习资料)