S07 · 系统设计:模块化、接口与设计模式


第 7 周|主打科目:综合知识 + 案例分析 + 论文|对应知识域:系统分析与设计(SA/SD、OOA/OOD)、软件架构设计|主打基本问题:Q2(架构怎么落地?)|本阶段产出:概要设计说明书(模块结构图 + 耦合内聚分析表 + 接口契约 + 设计模式选用表)

项目宪法见 00-课程总纲.md;本阶段对应综合知识中权重最高的模块(18–22%),见 02-考点地图.md

一、情境开场(Hook)

S06 的架构评审通过后的第三天,开发组长拿着架构图来找你,把图纸往桌上一摊,问了三个问题:

“第一,采集适配器我到底怎么定接口?设备部手里现在有 6 类协议、30 多个型号,明年还要加 S7-1500 和两个新厂家的私有协议。我总不能每加一个就改一次核心代码吧?”

“第二,追溯报告的导出格式,现在要 PDF,质量部说年底要加 Excel 和 XML,后年还要对接客户的 EDI 平台。我是不是现在就该把三种都写了?

“第三,报工界面。操作工平均年龄 46 岁,已经在老 MES 上点了 12 年。 我们新界面做得再漂亮,他们第一天抵触,第二天就用回纸质单子了。这界面到底怎么设计?”

你翻开 S06 那张架构图——分层清楚、风格明确、ADR 齐全,但这三个问题一个也答不了。架构图告诉你”有采集层、有服务层、有应用层”,却没告诉你采集层的接口长什么样、服务层的类怎么划分、应用层的人机界面怎么落地

那一刻你意识到:架构是”骨架”,设计是”关节与韧带”。骨架画对了,关节没设计好,系统照样动不了。

而用来衡量”关节好不好”的那把尺子,其实只有两个词——耦合与内聚

二、为什么学这一周(Where & Why)

  1. 耦合七种与内聚七种的排序:综合知识每年 2–4 题,且是年年考、必考、送分题排序表必须背到条件反射——这是全卷性价比最高的 4 分。
  2. 概要设计 vs 详细设计:综合知识 1–2 题;案例”某工作产品属于哪个设计阶段”偶考;论文中”设计阶段”段落的结构依据
  3. SOLID 与面向对象设计原则:综合知识 2–4 题;案例”指出某设计违反了什么原则并改正”约 6–10 分;论文”面向对象设计”类题目的核心素材
  4. 23 种 GoF 设计模式的分类与选用:综合知识 3–5 题(**”下列哪个属于行为型模式”是固定问法**);案例”针对某场景应选用哪种设计模式”约 6–10 分。
  5. 接口契约与 RESTful API:综合知识 2–3 题;案例”接口设计”在 Web/微服务类案例中出现频率极高,约 8–12 分。
  6. 熔断/限流/降级/幂等:综合知识 2–3 题,是近年微服务类案例的新增高频点。
  7. 人机界面设计原则:综合知识 1–2 题,纯记忆;同时是本项目”操作工平均 46 岁”这个真实约束的唯一解法来源。

三、核心讲义(Equip)

3.1 概要设计与详细设计

定义:软件设计(Software Design)从需求规格说明书出发,分为概要设计(Preliminary Design / 结构设计)详细设计(Detailed Design)两个阶段。

维度 概要设计 详细设计
核心任务 把需求转化为软件体系结构划分模块、确定模块功能与调用关系、确定模块接口、确定全局数据结构与数据库结构 每个模块确定算法与局部数据结构、确定接口细节、设计人机界面细节
回答的问题 系统由哪些部分组成、它们怎么连 每个部分内部怎么做
主要依据 需求规格说明书(SRS) 概要设计说明书
主要产出 概要设计说明书(系统结构图/层次图 + HIPO 图、模块划分与功能说明、接口定义、全局数据结构、数据库概要、测试计划) 详细设计说明书(各模块的算法描述、局部数据结构、接口细节、测试用例)
设计者 系统分析师 / 架构师 / 高级设计师 设计师 / 开发工程师
评审重点 结构的合理性、模块独立性、接口清晰度 算法正确性、数据结构合理性、可实现性

判定口诀:**”划模块、定接口 → 概要;写算法、定细节 → 详细。”** 考题问”确定模块的算法”→ 详细设计;问”划分模块并定义模块间接口”→ 概要设计。

详细设计的表达工具(综合知识常考辨析)

工具 特点
程序流程图(Flowchart) 最直观,但允许随意的控制转移(箭头乱跳),易破坏结构化
N-S 图 / 盒图(Nassi-Shneiderman) 完全结构化,无箭头,控制结构用盒子嵌套表示,强制结构化设计
PAD 图(Problem Analysis Diagram,问题分析图) 二维树形结构,自上而下、从左向右展开,结构清晰、易于翻译成代码,支持逐步求精
伪代码 / PDL(Program Design Language) 用类自然语言描述算法,最贴近实现
判定表 / 判定树 适合多条件组合的复杂逻辑,条件多时判定表优于流程图

3.2 模块独立性:耦合与内聚

定义模块独立性(Module Independence)指每个模块只完成系统要求的独立子功能,与其他模块的联系最少且接口简单。衡量标准只有两个:耦合(Coupling,模块之间)与内聚(Cohesion,模块内部)。

设计原则:力争高内聚、低耦合。

耦合七种(由低到高,必须背熟排序)

排序 耦合类型 定义 本项目例子
1(最低) 非直接耦合 两模块没有直接关系,联系完全通过主模块的控制与调用实现 设备台账维护追溯报告导出 互不感知,都由主菜单调用
2 数据耦合 两模块通过参数交换信息,且传递的是简单数据项 追溯服务.查询(批次号, 深度)——只传两个参数
3 标记耦合 两模块通过参数传递数据结构/记录,而被调用模块只用了其中一部分 把整个 Batch 对象传给 导出报告(),但导出只用其中 3 个字段
4 控制耦合 传递的是控制信息/开关量,被调用模块的行为取决于该控制信息 采集(mode),mode=1 全量、mode=2 增量,函数内部按 mode 分支
5 外部耦合 模块通过外部环境耦合(共享的全局变量、约定的文件格式、通信协议、设备接口),无显式接口 采集程序与边缘网关约定”按 /data/imp/*.csv 的目录与命名规范交换数据”
6 公共耦合 多个模块访问同一个公共数据环境(全局数据结构、共享内存、共享文件、公共数据库表) 5 个模块共享同一个全局 系统配置 结构体,任一模块改结构全体要改
7(最高) 内容耦合 一个模块直接访问另一模块的内部数据非正常入口转入内部代码重叠、或多个入口 采集模块直接读写追溯模块的内部缓存数组

记忆口诀:**”非数标控外公内”(非直接、数据、标记、控制、外部、公共、内容)。
实践目标:做到
数据耦合为佳;控制耦合、外部耦合、公共耦合要尽量减少;内容耦合坚决避免**。

内聚七种(由低到高,必须背熟排序)

排序 内聚类型 定义 本项目例子
1(最低) 偶然内聚 / 巧合内聚 模块内各成分之间毫无关联,纯粹偶然凑在一起 一个”工具模块”里同时放着日期格式化、日志写文件、二维码生成
2 逻辑内聚 各成分逻辑上相似,由参数决定执行哪一个 读取输入(类型),type=1 读键盘、type=2 读文件、type=3 读网络
3 时间内聚 / 瞬时内聚 各成分在同一时间段内执行,但彼此无必然联系 系统初始化 模块:加载配置、建立连接池、初始化缓存、启动定时任务
4 过程内聚 各成分按特定顺序(过程步骤)执行,但彼此之间无数据传递 日报生成:先锁表→再统计→再发邮件,三步顺序固定但不传数据
5 通信内聚 / 信息内聚 各成分操作同一数据集使用同一输入/输出 批次统计:计算批次合格率、计算批次工时、计算批次废料率——都读同一份批次数据
6 顺序内聚 一个成分的输出是下一个成分的输入,成分间存在数据流 采集流水线:读取报文 → 解析 → 校验 → 入库,每步输出是下一步输入
7(最高) 功能内聚 模块内所有成分共同完成一个单一功能,缺一不可 计算批次追溯链条哈希值——所有代码只做这一件事

记忆口诀:**”偶逻时过通顺功”(偶然、逻辑、时间、过程、通信、顺序、功能)。
顺序内聚 vs 过程内聚(高频辨析)
顺序内聚有数据流(前一步输出 = 后一步输入),过程内聚只有控制流(只是按顺序执行,互不传数据)。通信内聚 vs 顺序内聚**:通信内聚是”共享同一份数据“,顺序内聚是”数据被依次加工“。

耦合与内聚的对应关系:模块内聚越高,则模块间的耦合往往越低;但二者不是同一件事的两面——可以出现”高内聚但耦合也高”(一个功能完整的模块依赖了大量全局数据)。独立性 = 高内聚 + 低耦合,两个都要检查。

3.3 面向对象设计原则:SOLID 与两补充原则

定义:设计原则(Design Principles)是比设计模式更根本的指导思想——**模式是原则在具体场景下的固定解法,原则是模式的”所以然”**。

原则 定义 违反的例子(本项目) 修正
S 单一职责原则(SRP) 一个类应该只有一个引起它变化的原因 BatchService 同时负责追溯查询、报告导出、PDF 生成、邮件发送——PDF 库升级也要改它 拆为 TraceQueryServiceReportAssemblerPdfRendererNotificationService
O 开闭原则(OCP) 软件实体应对扩展开放、对修改关闭 每新增一种设备协议,就在 采集() 里加一个 else if 分支 定义 IDeviceAdapter 接口,新增协议只新增实现类,不改任何已有代码
L 里氏替换原则(LSP) 子类型必须能替换掉它们的基类型(基类出现的地方,子类都能出现且行为符合预期) 子类 文件导出设备 覆盖父类的 连接() 时直接抛 不支持操作异常 把”连接”从基类抽走,改为可选能力接口 IConnectable,不支持的设备不实现它
I 接口隔离原则(ISP) 客户端不应依赖它不需要的接口;依赖应建立在最小接口 IDevice 接口有 20 个方法,文件导出设备 被迫实现 15 个空方法 拆为 IReaderIWriterIConfigurable 等细粒度接口,各设备按需实现
D 依赖倒置原则(DIP) 高层模块不依赖低层模块,二者都依赖抽象抽象不依赖细节,细节依赖抽象 追溯服务 直接 new PostgreTraceDao(),换数据库要改服务层 服务层依赖 ITraceDao 抽象,具体实现由依赖注入容器装配
迪米特法则(LoD / 最少知识原则) 一个对象应当对其他对象了解最少只与直接朋友通信(自身、方法参数、成员对象、自己创建的对象) report.getBatch().getWorkOrder().getEquipment().getName() —— 链式穿越四层 报告 上提供 getEquipmentName(),由报告自己向内部协作对象索取
合成复用原则(CRP) 优先使用对象组合/聚合(has-a)而非继承(is-a)实现复用 为复用 ArrayList 的排序,让 追溯链条 继承 ArrayList,结果继承了 20 个不需要的方法 追溯链条 持有 List<TraceNode> 成员并委派实现所需行为

LSP 的经典反例(年年考):**”正方形继承长方形”——正方形是特殊的长方形(is-a 成立),但 长方形.setWidth()setHeight() 独立设置,正方形必须同时改变两者,子类改变了父类的行为契约,故违反 LSP,不能用继承。
判定口诀
继承的合法性不看”是不是”,看”行为契约是否保持一致”。**

3.4 23 种 GoF 设计模式

定义:设计模式(Design Pattern)是被反复验证的、针对特定场景的可复用设计方案。GoF(Gang of Four,四人组)的《设计模式》提出 23 种,分创建型、结构型、行为型三类。

分类表(必须能默写出每个模式的归属)

类型 数量 模式
创建型(Creational) 5 单例(Singleton)、工厂方法(Factory Method)、抽象工厂(Abstract Factory)、建造者(Builder)、原型(Prototype)
结构型(Structural) 7 适配器(Adapter)、装饰器(Decorator)、代理(Proxy)、外观(Facade)、桥接(Bridge)、组合(Composite)、享元(Flyweight)
行为型(Behavioral) 11 策略(Strategy)、模板方法(Template Method)、观察者(Observer)、迭代器(Iterator)、责任链(Chain of Responsibility)、命令(Command)、状态(State)、访问者(Visitor)、中介者(Mediator)、备忘录(Memento)、解释器(Interpreter)

记忆口诀创建 5 —— “单工抽建原”;结构 7 —— “适装代外桥组享”;行为 11 —— “策模观迭责命状,访中备解”。
数量一定要记死:5 / 7 / 11 = 23。 考题最爱把行为型模式混进结构型的选项里。

创建型 5 种(逐个精讲)

模式 结构与一句话 本项目落点
单例 保证一个类仅有一个实例并提供全局访问点(私有构造 + 静态获取方法,多线程需双重检查锁定) 设备连接池管理器、配置管理器、序号生成器
工厂方法 定义创建对象的接口,让子类决定实例化哪个类(一个工厂只产一类产品) DeviceAdapterFactory,由 ModbusAdapterFactoryOpcUaAdapterFactory 各自创建对应适配器
抽象工厂 提供创建一系列相关或相互依赖对象的接口,无需指定具体类(一个工厂产一族产品) IDaoFactory 同时产出 IBatchDaoIInspectDaoITraceDao,切换数据库时整族替换
建造者 复杂对象的构建与其表示分离,使同样的构建过程可创建不同表示(分步构造 + build() 追溯报告组装:按方向、深度、格式分步添加节点,最后 build() 出 PDF / Excel / XML 不同表示
原型 用原型实例指定创建对象的种类,通过拷贝(clone)创建新对象(避免重复的构造开销) 检验模板、工艺路线的复制——从既有模板 clone 后改少量属性

结构型 7 种

模式 结构与一句话 本项目落点
适配器 把一个类的接口转换成客户希望的另一个接口,解决接口不兼容(对象适配器用组合,类适配器用继承) 本项目最关键的模式:把 6 类私有协议统一适配为 IDeviceAdapter;老设备文件导出也能接入
装饰器 动态地给对象叠加职责,比生成子类更灵活(装饰器与被装饰对象实现同一接口,可递归嵌套) 给采集数据流叠加 校验 → 压缩 → 加密 → 缓冲,按需组合,不改原有类
代理 为对象提供代理以控制对它的访问(远程代理、虚拟代理、保护代理、智能引用) 远程设备代理(屏蔽通信细节)、采集结果的懒加载虚拟代理、按角色的保护代理
外观 为子系统中的一组接口提供一致的简化界面,降低客户端与子系统的耦合 TraceFacade 对外只暴露 queryTrace(batchNo),内部协调 5 个服务
桥接 抽象部分与实现部分分离,使二者可独立变化(用组合代替多层继承,解决二维变化 追溯算法(正向/反向/双向)× 导出格式(PDF/Excel/XML) 二维扩展时,避免 3×3=9 个子类
组合 把对象组合成树形结构表示”部分—整体”,使客户端对单个对象与组合对象使用一致(叶子与容器同接口) BOM 物料清单树、追溯链条的嵌套节点(节点可含子节点)
享元 共享技术支持大量细粒度对象,分离内部状态(共享)外部状态(不共享) 400 台设备、百万级采集点位的元数据定义(型号、单位、量程)共享为享元,具体读数作为外部状态

行为型 11 种

模式 结构与一句话 本项目落点
策略 定义一系列算法并封装,使它们可相互替换,算法的变化不影响使用算法的客户 检验判定规则:尺寸判定、热处理判定、探伤判定各自封装为策略,新增规则不改判定引擎
模板方法 定义算法骨架,把某些步骤延迟到子类(父类控制流程,子类填细节) 采集流程连接 → 读数据 → 解析 → 校验 → 入库 骨架固定,各协议只需实现”读数据”与”解析”
观察者 定义对象间一对多依赖,一个对象状态改变时所有依赖者自动收到通知(推/拉两种模型) 设备参数超阈值 → 自动推送报警给看板、短信、质量部三个观察者
迭代器 提供一种方法顺序访问聚合对象的元素而不暴露其内部表示 遍历追溯链条节点、遍历 BOM 树(统一迭代接口)
责任链 使多个对象都有机会处理请求,把处理者连成链并沿链传递,直到有人处理(解耦发送者与接收者 不合格品审批流:检验员 → 工艺员 → 质量经理 → 厂长,按金额自动决定链长
命令 请求封装成对象,从而可对请求排队、记录日志、撤销/重做、支持事务 采集指令队列(可重放、可撤销)、报工的撤销操作、断网补传的命令日志
状态 允许对象在内部状态改变时改变行为,把状态判断逻辑分散到各状态类中(消除大量的 if-else 工单状态机 / 批次状态机(已创建→生产中→待检→合格/不合格→已入库/已报废)
访问者 不改变元素类的前提下,定义作用于这些元素的新操作(双分派) 对追溯节点做导出、统计、校验、脱敏等多种操作,新增操作不改节点类
中介者 用一个中介对象封装一组对象的交互,使对象间不必显式相互引用(网状变星形 多设备网关的协同调度,各网关只与中介者通信,避免两两耦合
备忘录 不破坏封装的前提下捕获对象内部状态并在需要时恢复 检验记录的草稿保存与回滚、多步填报的”上一步”
解释器 给定文法,定义其文法表示解释器(终结符/非终结符表达式 + 上下文) 报警阈值规则表达式解析(如 温度 > 80 AND 持续 > 30s)、自定义追溯过滤条件

创建型 vs 结构型 vs 行为型的快速判定(案例”应选哪种模式”的答题思路)

  • 问”怎么创建对象 / 对象创建过程复杂 / 希望隔离创建细节“ → 创建型
  • 问”两个已有接口不兼容 / 要给对象动态加功能 / 要简化复杂子系统 / 要处理树形结构“ → 结构型
  • 问”对象之间怎么协作与职责分配 / 算法要可替换 / 状态改变要改变行为 / 请求要在多个处理者间传递“ → 行为型

模式辨析(高频)

对比 区别
适配器 vs 装饰器 vs 代理 适配器改变接口、装饰器增加职责、代理控制访问;装饰器强调递归叠加,代理通常不叠加
外观 vs 中介者 外观是单向的(客户端→子系统,简化调用);中介者是双向的(协调各同事对象之间的交互)
策略 vs 状态 策略由客户端主动选择(算法切换);状态由对象内部状态自动切换(客户端不知情)
桥接 vs 适配器 桥接是设计前为多维度变化预留;适配器是设计后补救已有接口不兼容
命令 vs 策略 命令封装请求(可排队、可撤销、可日志);策略封装算法(可替换)
工厂方法 vs 抽象工厂 工厂方法产一种产品(一个工厂一个方法);抽象工厂产一族产品(多个相关方法)

3.5 接口设计:契约、RESTful 与稳定性

接口契约(Design by Contract,契约式设计)

要素 定义 本项目的 采集适配器 示例
前置条件(Precondition) 调用方在调用前必须保证为真的条件;不满足时,被调用方无义务保证结果 调用 read() 前,connect() 必须已成功返回;设备编号非空且在台账中存在
后置条件(Postcondition) 被调用方在返回时保证为真的条件(前提是前置条件已满足) read() 返回非 null 的读数集合;若返回失败,异常中包含可区分的错误码
不变式(Invariant) 在对象整个生命周期(调用前后)始终为真的约束 适配器实例的 协议类型 创建后不可更改;已采集序号 单调递增

责任划分前置条件是调用方的义务,后置条件是被调用方的义务,不变式是双方的共同约束。 案例问”接口契约包括哪些”→ 答这三项。

RESTful API 设计

要素 规范
资源 名词复数表示资源(/batches/batches/{batchNo}/inspections),不在 URI 中放动词
HTTP 动词 GET 查询(安全且幂等)、POST 创建(非幂等)、PUT 整体更新(幂等)、PATCH 部分更新(非幂等)、DELETE 删除(幂等)
状态码 200 成功、201 创建成功、204 无内容;400 请求参数错误、401 未认证、403 无权限、404 资源不存在、409 冲突(如版本冲突)、429 限流、500 服务器内部错误、503 服务不可用
幂等性 同一请求执行一次与多次的效果相同GET/PUT/DELETE 天然幂等;POST 不幂等,需额外设计
版本管理 URL 路径(/api/v1/batches)最常见;也可用请求头或查询参数
无状态 服务端不保存客户端上下文,每个请求自带全部必要信息
HATEOAS 响应中带上可执行的后续操作链接,客户端无需硬编码(成熟度模型最高级)

幂等设计(微服务类案例高频)

  • 唯一请求号 / 幂等令牌:客户端生成全局唯一号,服务端去重(Redis SETNX 或数据库唯一索引)
  • 数据库唯一约束:如 (批次号, 工序号, 报工时间) 唯一,重复提交直接冲突
  • 乐观锁UPDATE ... WHERE version = ?,或带时间戳条件
  • 状态机前置校验:报工前校验批次状态是否允许报工,已完成的直接拒绝
  • Token 机制:先取 token,提交时校验并删除

重试与幂等(必须成对出现)重试必须配合幂等,否则就是放大故障。 重试策略:指数退避(1s/2s/4s/8s…)、重试上限(通常 3 次)、区分可重试错误(网络超时、503、429)与不可重试错误(400、401、403、404)、幂等键贯穿全程

API 网关(API Gateway):统一入口,承担路由、认证鉴权、限流、熔断、日志审计、协议转换、灰度发布、请求聚合。是”智能端点、哑管道”中的”哑管道”部分。

稳定性三件套(近年高频)

机制 定义 三态 / 算法 本项目落点
熔断(Circuit Breaker) 当依赖的失败率达到阈值时,直接快速失败,不再调用下游,避免雪崩 关闭(正常)→ 打开(失败率超阈值,直接拒绝)→ 半开(放行部分请求试探,成功则关闭,失败则回到打开) 追溯服务调用 ERP 接口连续失败时熔断,改为返回”外部数据暂不可用”的降级响应
降级(Degradation) 关闭非核心功能,保住核心链路 按功能分级:核心(报工、检验)不可降级;非核心(看板统计、导出)可降级 高峰期关闭”导出 PDF”与”历史趋势图”,保证报工与检验可用
限流(Rate Limiting) 控制单位时间内的请求量,防止系统被压垮 令牌桶(允许突发)、漏桶(恒定速率,平滑流量)、计数器(固定窗口/滑动窗口) 追溯查询单用户限 10 QPS;导出接口全局限 5 并发

微服务拆分(案例高频)

  • 按业务能力(Business Capability):按企业做什么拆——生产执行、质量管理、设备管理、主数据
  • 按限界上下文(Bounded Context,DDD):按领域模型的一致性边界拆——同一个”物料”在”生产”与”采购”两个上下文中含义不同,应分属不同服务
  • 拆分原则单一职责团队自治(两个披萨团队)、独立数据源(Database per Service)高内聚低耦合、避免分布式单体(服务拆了但数据库还共享,一改一起发版)
  • 不拆的信号:团队规模小(本项目运维 2 人)、事务强一致要求高、领域边界尚未稳定、缺少自动化运维能力

本项目立场不做微服务拆分,采用模块化单体(Modular Monolith)——按限界上下文划分模块(生产执行、质量追溯、设备采集、主数据),模块间通过明确的接口 + 事件交互,但部署为一个应用。理由:运维只有 2 人,且模块边界目前尚不稳定(老设备接入方式还在变)。保留未来拆分的能力:模块间不共享数据库表、不跨模块直接访问对方的表——这样将来真要拆,是”拆部署”而不是”拆代码”。

3.6 人机界面设计原则

黄金三规则(Shneiderman)

原则 要求 本项目落点
① 置于用户控制之下(用户控制权) 用户能发起与控制操作:可取消、可撤销、可定制;不以模态框强迫用户;不越俎代庖替用户决策 报工提交后 30 秒内可撤销(操作工会手抖填错数量);批量导入前提供”预检结果”让用户确认,而不是直接导入
② 减少用户的记忆负担 提供默认值直观的快捷方式、图形化识别(认 > 记)、界面布局基于真实世界的隐喻 报工界面默认带出上一条的班组/设备/工序,操作工只需填数量与不良数;工序用图标 + 名称而非仅编码
③ 保持界面一致 术语一致、操作步骤一致、布局与配色一致;同一操作在所有界面产生相同效果 三个厂区的界面完全一致;”保存”按钮永远在右下角;错误提示永远用同一套红黄配色与措辞

扩展原则(补充四条)

  • 及时反馈:任何操作都有可见响应;长任务显示进度条预计剩余时间;错误提示要可理解、可恢复、可定位(说明”哪里错了、怎么改”,而不是”操作失败”)
  • 容错性设计:危险操作二次确认;输入即时校验(而不是提交后才报错);提供撤销;数值输入带合理范围提示
  • 减少认知负荷:一屏只做一件事;相关信息就近分组;默认展开常用项、折叠高级项
  • 为真实用户设计:面向操作工平均 46 岁、用老 MES 12 年的群体——字号不小于 14px、按钮触控区不小于 44×44、关键数字用大号字体、保留老界面的关键快捷键(如 F2 报工、F4 查询),并提供过渡期双轨运行(新旧界面并存一个月)

本项目真实约束的处理:不要指望”培训一次就会”。应该做的是——新界面的第一版刻意保留老 MES 的操作顺序与快捷键,先做”换个皮肤”,稳定后再逐步优化交互;同时在每个厂区培养 2 名”种子用户”参与设计评审。人机界面设计的失败,90% 不是因为难看,而是因为”改了用户已经形成的肌肉记忆”。

四、典型考法

4.1 综合知识怎么考

题 1:下列耦合类型中,耦合度由低到高排列正确的是(  )。 A. 数据—非直接—标记—控制 B. 非直接—数据—标记—控制 C. 数据—标记—控制—非直接 D. 非直接—标记—数据—控制
答案:B。解析:耦合由低到高为非直接耦合 < 数据耦合 < 标记耦合 < 控制耦合 < 外部耦合 < 公共耦合 < 内容耦合。口诀”非数标控外公内“。

题 2:模块内部各成分”前一成分的输出是后一成分的输入”属于(  )。 A. 过程内聚 B. 通信内聚 C. 顺序内聚 D. 功能内聚
答案:C。解析:顺序内聚的关键特征是成分间存在数据流(输出即输入);过程内聚只有控制流(按序执行但不传数据);通信内聚是各成分共享同一数据集

题 3:某系统中,为了让客户端能够方便调用由多个子系统组成的复杂功能,提供了一个统一的简化接口。该设计采用了(  )。 A. 适配器模式 B. 外观模式 C. 中介者模式 D. 桥接模式
答案:B。解析:外观模式为子系统提供一致的简化界面,单向简化客户端调用;中介者是双向协调同事对象之间的交互;适配器解决接口不兼容;桥接分离抽象与实现。

题 4:关于面向对象设计原则,下列说法错误的是(  )。 A. 依赖倒置原则要求高层模块与低层模块都依赖于抽象 B. 里氏替换原则要求子类可以替换父类出现在任何地方 C. 接口隔离原则要求客户端依赖尽可能大的统一接口 D. 合成复用原则建议优先使用组合而非继承
答案:C。解析:接口隔离原则要求客户端不依赖它不需要的接口,应建立在最小接口上,与 C 的说法相反。

题 5:设计要求”每新增一种设备通信协议,只需新增一个实现类并注册,不修改任何已有代码”,这主要体现了(  ),可采用的模式是(  )。 A. 单一职责 / 策略 B. 开闭原则 / 工厂方法 + 适配器 C. 里氏替换 / 装饰器 D. 迪米特法则 / 外观
答案:B。解析:对扩展开放、对修改关闭 = 开闭原则;实现手段是工厂方法(负责创建)+ 适配器(把私有协议转换为统一接口)的组合。

4.2 案例分析怎么考

【案例题】(共 25 分)

承接本项目。系统分析师正在进行概要设计。已知:
① 采集层需支持 6 类协议(Modbus RTU/TCP、OPC DA、OPC UA、S7、文件导出、以及未来新增的私有协议),新增协议时不得修改核心代码
② 追溯报告需要支持 PDF / Excel / XML 三种导出格式,未来还将对接客户 EDI 平台;追溯算法支持正向 / 反向 / 双向三种。
③ 不合格品处理需经过”检验员 → 工艺员 → 质量经理 → 厂长”的审批,审批层级按不合格品金额自动决定(< 1 万由工艺员终审,1–10 万由质量经理终审,≥ 10 万需厂长审批)。
④ 操作工平均年龄 46 岁,已使用老 MES 12 年,熟悉其操作顺序与快捷键。
⑤ 追溯服务需要调用 ERP 系统的库存接口获取实时库存;该接口偶发超时(约 2% 请求超过 5 秒)。

【问题 1】(9 分) 已知原设计中有如下模块:模块 A”工具模块”(含日期格式化、日志写入、二维码生成);模块 B”系统初始化”(加载配置、建立连接池、启动定时任务);模块 C”日报生成”(锁表 → 统计 → 发邮件,三步顺序固定且互不传数据);模块 D”批次统计”(计算合格率、计算工时、计算废料率,三者均读取同一份批次数据);模块 E”采集流水线”(读报文 → 解析 → 校验 → 入库,每步输出是下一步输入)。请分别判断这五个模块的内聚类型,并按内聚程度由低到高排序
【问题 2】(10 分) 针对需求①②③,分别指出应采用哪种(或哪些)设计模式,并结合本项目说明为什么选它、以及它与相近模式的区别
【问题 3】(6 分) 针对需求④⑤,请回答:① 人机界面设计应遵循哪些原则?结合”操作工平均 46 岁”这一约束给出至少 3 条具体措施。② 调用 ERP 接口偶发超时,应采取什么措施?请说明设计要点。

采分点拆解

  • 问题 1(9 分):五个模块判断各 1.5 分(类型 1 分 + 理由 0.5 分);排序 1.5 分只写类型不写理由的每处扣 0.5 分;排序错误整体扣 1.5 分。
  • 问题 2(10 分):需求① 4 分(模式名称 2 分 + 理由 2 分);需求② 3 分(模式名称 1.5 分 + 理由 1.5 分);需求③ 3 分(模式名称 1.5 分 + 理由 1.5 分)。必须说明”与相近模式的区别”,否则每处扣 1 分。
  • 问题 3(6 分):原则 2 分(三条黄金规则各 0.5,扩展 0.5);具体措施 2 分(≥3 条,且必须结合”46 岁 / 12 年老系统”的约束,泛泛而谈最多 1 分);ERP 超时措施 2 分(必须同时答出超时控制、重试、熔断/降级三类中的至少两类)。

标准作答范例

【问题 1】

模块 内聚类型 理由
A 工具模块 偶然内聚(巧合内聚) 日期格式化、日志写入、二维码生成三者之间毫无业务关联,纯粹因为”都是工具方法”被凑在一起,是最低的内聚
B 系统初始化 时间内聚(瞬时内聚) 加载配置、建立连接池、启动定时任务三者彼此无必然联系,只是都在系统启动这一时间段内执行
C 日报生成 过程内聚 锁表 → 统计 → 发邮件三步顺序固定,但彼此之间没有数据传递(锁表不产生统计的输入),只存在控制流
D 批次统计 通信内聚(信息内聚) 合格率、工时、废料率三个计算操作同一份批次数据(共享同一输入数据集),但彼此独立、互不依赖对方的输出
E 采集流水线 顺序内聚 读报文 → 解析 → 校验 → 入库中,每一步的输出都是下一步的输入,成分间存在明确的数据流

内聚程度由低到高排序A(偶然)< B(时间)< C(过程)< D(通信)< E(顺序)

说明:五个模块均未达到功能内聚(最高级)。改进方向:把 A 按职责拆成三个独立模块;把 D 的三个计算合并为一个”批次指标计算”模块以提升为功能内聚;E 的流水线上若再加上”只做一件事”的边界,可通过重构达到功能内聚。

【问题 2】

① 需求(6 类协议,新增协议不改核心代码)→ 采用【工厂方法模式】+【适配器模式】的组合。

  • 适配器模式负责解决接口不兼容:每种私有协议实现一个 XxxAdapter,把该协议的私有调用方式转换为统一的 IDeviceAdapter 接口(含 connect() / read() / write() / close()),核心代码只面向接口编程。
  • 工厂方法模式负责延迟实例化:由 ModbusAdapterFactoryOpcUaAdapterFactoryFileImportAdapterFactory 等具体工厂各自创建对应适配器;新增协议时只需新增一个适配器类 + 一个工厂类,核心代码零修改——这正是开闭原则(对扩展开放、对修改关闭)的落地。
  • 与相近模式的区别适配器改变接口(解决不兼容),而装饰器增加职责代理控制访问,三者外形相似但意图完全不同;工厂方法一种产品(一个工厂一个创建方法),抽象工厂一族产品——本题只需要生产适配器这一类对象,故用工厂方法而非抽象工厂。

② 需求(3 种导出格式 × 3 种追溯算法,未来还要加)→ 追溯算法用【策略模式】,导出格式用【桥接模式】(也可答策略 + 建造者组合)。

  • 策略模式用于追溯算法:把正向、反向、双向三种追溯算法封装为独立策略类,实现同一 ITraceStrategy 接口;调用方按需注入,新增算法只需新增策略类,不改追溯引擎——消除引擎中的 if-else 分支。
  • 桥接模式用于二维变化:追溯维度(算法)与导出表示(格式)是两个独立变化的维度。若用继承,需要 3×3=9 个子类;用桥接把抽象部分(追溯报告)与实现部分(渲染器)分离并用组合连接,只需 3+3=6 个类,且两边可独立扩展。
  • 与相近模式的区别桥接是设计前为多维度变化预留结构,适配器是设计后补救已有接口不兼容——本题两个维度都是已知且将持续扩展的,属设计期问题,故用桥接而非适配器。策略由客户端主动选择算法状态由对象内部状态自动切换——本题的追溯方向由用户显式选择,故是策略而非状态。

③ 需求(审批层级按金额动态决定)→ 采用【责任链模式】。

  • 定义抽象处理者 ApprovalHandler(含 setNext()handle()),由 检验员工艺员质量经理厂长 四个具体处理者构成链;请求沿链传递,每个处理者判断金额是否落在自己的权限区间内:在自己权限内则处理并终止,否则转交下一个。
  • 优势:发送者不需要知道最终由谁处理(解耦),新增审批层级或调整金额阈值只需增删/调整链节点,不改任何已有处理者,符合开闭原则。
  • 与相近模式的区别责任链中请求沿链传递、由某个处理者处理(发送者不知谁处理);命令模式是把请求封装成对象以便排队/撤销/记录日志,二者意图不同——本题要求”按规则自动决定由谁审批、发送者无需关心”,故是责任链而非命令。

【问题 3】

① 人机界面设计原则与针对”46 岁 / 12 年老系统”的具体措施

三条黄金规则置于用户控制之下(可取消、可撤销、可定制);减少用户记忆负担(默认值、直观快捷方式、图形化识别优于记忆);保持界面一致(术语、步骤、布局一致,同一操作产生相同效果)。

针对本项目的 3 条具体措施

  • 保留肌肉记忆:新界面第一版刻意沿用老 MES 的操作顺序与快捷键(如 F2 报工、F4 查询、Esc 取消),先做到”换个皮肤就能上手”,稳定后再逐步优化交互——不要一次性改掉用户 12 年形成的肌肉记忆
  • 适老化与容错:正文字号不小于 14px、关键数值(数量、不良数)用大号加粗字体;按钮点击区不小于 44×44 像素;数值输入框带合理范围提示即时校验(填错当场提示,而不是提交后才报错);报工提交后提供 30 秒内撤销通道(应对手抖填错)。
  • 参与式设计 + 双轨过渡:每个厂区选 2 名一线操作工作为”种子用户”参与界面评审;新系统上线后新旧界面并行一个月,操作工可随时切回老界面;期间收集反馈迭代,一个月后根据使用率决定是否下线老界面。

② ERP 接口偶发超时(2% 请求 > 5 秒)的处理措施

采用超时控制 + 重试 + 熔断 + 降级的组合:

  • 超时与重试:设置连接超时 1 秒、读超时 3 秒(必须设置超时,不设超时的重试会耗尽线程);对可重试错误(超时、503、429)做最多 2 次重试并采用指数退避(1s、2s);对不可重试错误(400/401/403/404)直接失败不做重试
  • 熔断:统计最近 1 分钟内的失败率,超过 50% 即熔断,在随后 30 秒内直接快速失败,不再调用 ERP,避免线程池被拖垮;30 秒后进入半开状态放行少量请求试探,成功则关闭熔断,失败则重新打开。
  • 降级:熔断期间的追溯报告改为返回”实时库存暂不可用,展示截至最近一次成功同步的库存(并标注数据时间)”,保证核心的追溯链条完整可用——宁可少一个字段,不能整份报告打不开
  • 幂等与去重:重试的查询请求携带唯一请求号,服务端做去重,避免重试造成重复记账。

五、易错点(Rethink)

错误认知 为什么错 正确理解
“顺序内聚和过程内聚一样,都是按顺序执行” 忽略了数据流的存在 顺序内聚有数据流(前一步输出 = 后一步输入);过程内聚只有控制流(按序执行但不传数据)。这是两者唯一也是决定性的区别
“通信内聚比顺序内聚高” 排序记反 通信(5)< 顺序(6);通信只是”共享同一数据集”,顺序是”数据被依次加工”,后者联系更紧密
“外部耦合比公共耦合高” 排序记反 外部耦合(5)< 公共耦合(6);公共耦合是多个模块共享公共数据环境,影响面更大
“标记耦合比数据耦合低” 混淆了”传结构”与”传数据项” 数据耦合(2)< 标记耦合(3);标记耦合传递了整个数据结构但只用了其中一部分,是不必要的依赖
“高内聚自然就低耦合” 把两个维度混为一谈 二者相关但不等价:可以出现”高内聚 + 高耦合”(功能完整但依赖大量全局数据)。独立性必须两个都检查
“适配器、装饰器、代理差不多” 只看了结构外形 适配器改接口、装饰器加职责(可递归叠加)、代理控访问(不叠加),看意图不看形状
“外观和中介者都是’中间层’,可以互换” 方向不同 外观是单向的(客户端 → 子系统,简化调用);中介者是双向的(协调各同事对象之间的交互,网状变星形)
“策略和状态都能消除 if-else,随便选” 切换主体不同 策略由客户端主动选择算法;状态由对象内部状态自动切换、客户端不知情
“接口隔离就是少定义接口” 方向理解反了 接口隔离是把一个臃肿接口拆成多个专用接口,让客户端只依赖它需要的那一个——接口数量变多,但每个客户端依赖变少
“POST 也是幂等的” 混淆动词语义 GET / PUT / DELETE 幂等,POST 不幂等(每次都创建新资源);POST 要做幂等需额外加唯一请求号/幂等令牌
“重试就好,不需要幂等” 重试会放大故障 重试必须配合幂等,否则重复报工、重复扣款。且必须设超时 + 重试上限 + 指数退避 + 区分可重试错误
“熔断和降级是一回事” 层次不同 熔断是保护调用方(快速失败、防雪崩,三态机);降级是牺牲非核心功能保核心(业务决策,可主动触发)
“人机界面设计就是做得好看” 忽略了真实用户 本项目用户是46 岁、用了 12 年老系统的操作工保留肌肉记忆、容错、参与式设计比美观重要得多

六、GRASPS 推进(Experience)

本周五前,往交付物「概要设计说明书」里加三块内容,总计 1200–1500 字 + 两张表 + 一张图

  1. 模块结构图一张 + 耦合内聚分析表:结构图分不少于 3 层(子系统 → 模块 → 关键类),不少于 15 个模块;分析表至少覆盖 10 个模块,逐行写内聚类型 + 判断理由 + 与其他模块的耦合类型 + 改进措施。表中必须至少出现 2 处”当前耦合过高、需改进”的模块,并写出改进方案(改成什么耦合、怎么改)。
  2. 接口契约表不少于 6 个核心接口(如 IDeviceAdapterITraceServiceIReportRendererIApprovalHandlerITraceDaoIInventoryClient),每个接口写方法签名 + 前置条件 + 后置条件 + 不变式 + 错误码约定。其中 IDeviceAdapter 必须给出完整的接口定义代码(伪代码即可)
  3. 设计模式选用表不少于 8 个设计模式,每行写模式名 + 归属类型 + 解决的问题 + 本项目落点 + 为什么不用相近的那个模式

验收标准:模块结构图里任意挑两个模块,你能说出它们之间的耦合类型并解释”为什么是这个而不是更低的那一级”;接口契约表里每个接口的前置条件都由调用方负责、后置条件都由实现方负责,没有一条是”双方都要保证”的糊涂账;设计模式表里每个模式都能说出它解决的是”创建问题 / 结构问题 / 行为问题”中的哪一类;把报工界面原型给 2 名一线操作工试用,不提供任何说明的情况下,能在 3 分钟内独立完成一次报工——完不成,就是肌肉记忆没保留下来。

七、自测

  1. 软件设计中,”为每个模块确定算法与局部数据结构”属于(  )。 A. 需求分析 B. 概要设计 C. 详细设计 D. 编码
  2. 耦合度由低到高排列,正确的是(  )。 A. 非直接—数据—标记—控制—外部—公共—内容 B. 数据—非直接—标记—控制—公共—外部—内容 C. 非直接—标记—数据—控制—外部—公共—内容 D. 数据—标记—控制—非直接—外部—公共—内容
  3. 内聚度由低到高排列,正确的是(  )。 A. 偶然—逻辑—时间—过程—通信—顺序—功能 B. 偶然—时间—逻辑—过程—通信—顺序—功能 C. 逻辑—偶然—时间—过程—通信—顺序—功能 D. 偶然—逻辑—时间—通信—过程—顺序—功能
  4. “初始化模块”中同时包含”加载配置””建立连接池””启动定时任务”,其内聚类型是(  )。 A. 逻辑内聚 B. 时间内聚 C. 过程内聚 D. 功能内聚
  5. 下列模式中,属于创建型的是(  )。 A. 适配器 B. 装饰器 C. 建造者 D. 观察者
  6. 下列模式中,属于行为型的是(  )。 A. 桥接 B. 组合 C. 享元 D. 访问者
  7. “把一个类的接口转换成客户希望的另一个接口,解决接口不兼容”,描述的是(  )。 A. 适配器 B. 桥接 C. 代理 D. 外观
  8. 客户端不应依赖它不需要的接口,这描述的是(  )。 A. 单一职责原则 B. 接口隔离原则 C. 依赖倒置原则 D. 迪米特法则
  9. 下列 HTTP 方法中,不是幂等的是(  )。 A. GET B. PUT C. DELETE D. POST
  10. 熔断器的三种状态是(  )。 A. 启动—运行—停止 B. 关闭—打开—半开 C. 正常—异常—恢复 D. 初始—就绪—完成
题号 1 2 3 4 5
答案 C A A B C
一句话解析 详细设计 = 算法 + 局部数据结构 + 接口细节;概要设计 = 划模块 + 定接口 + 全局数据结构 口诀”非数标控外公内 口诀”偶逻时过通顺功 三者在同一时间段执行但彼此无联系 → 时间内聚(无数据流是判据) 创建型 5 种:单工抽建原(单例、工厂方法、抽象工厂、建造者、原型)
题号 6 7 8 9 10
答案 D A B D B
一句话解析 行为型 11 种:策略、模板方法、观察者、迭代器、责任链、命令、状态、访问者、中介者、备忘录、解释器;桥接/组合/享元是结构型 适配器 = 改接口解决不兼容;桥接是设计期分离抽象与实现;代理控访问;外观简化 接口隔离原则(ISP):依赖最小接口,不依赖不需要的方法 GET/PUT/DELETE 幂等,POST 不幂等(每次都创建新资源),需幂等令牌兜底 熔断三态:关闭(正常)→ 打开(快速失败)→ 半开(试探放行)

八、分档任务(Tailor)

  • 保底 45:背死 3.2 的耦合七种(非数标控外公内)内聚七种(偶逻时过通顺功)排序及各自定义、3.4 的 5/7/11 分类表;完成 4.1 五题与自测十题;案例 4.2 的【问题 1】能完整写出五个模块的内聚判断与排序。
  • 冲 60:完成 GRASPS 三件产出;案例 4.2 三问手写完整作答并自评;**能对每个 GoF 模式说出”它属于哪一型、解决创建/结构/行为中的哪一类问题、与最相近模式的区别”**(对着分类表逐行自检,卡住的超 3 个就重学 3.4)。
  • 冲 70:把本项目换成医疗场景(患者就诊 / 检验开单 / 医保结算)重做一遍设计模式选用表,要求至少 6 个模式与制造业场景的选择不同并说明为什么业务约束不同导致模式不同;为”追溯服务调用 ERP”设计一套完整的稳定性方案(超时/重试/熔断/降级/限流的具体参数与阈值,并说明每个阈值的依据);自命题一道”指出设计违反了什么原则并改正”的案例题并写出采分点。

九、本阶段回答基本问题

Q2(第二次追问):架构怎么落地?”好的架构”在代码层面长什么样?

第一,架构的承诺最终要由”模块独立性”来兑现,而独立性只有一把尺子——耦合与内聚。 S06 说”分层 + 局部事件驱动、新增协议不改核心”,这句话在架构图上是漂亮的,但它能不能兑现,取决于 IDeviceAdapter 这个接口是不是真的把协议差异关在了插件里(数据耦合而不是公共耦合),取决于采集流水线的四步是不是真的形成了顺序内聚而不是把逻辑散在五个地方。架构是承诺,设计是兑现;耦合与内聚是验收标准。

第二,设计原则是”所以然”,设计模式是”然”。 开闭原则说明的是”为什么新增协议不该改已有代码”;工厂方法 + 适配器只是它在”多协议接入”这个场景下的固定解法。只会背 23 个模式名字的人,遇到新场景选不出来;理解 SOLID 的人,即使忘了模式名,也能现场推导出一个像样的解法。 这就是为什么软考既考模式分类(记忆层)又考”指出违反了什么原则”(理解层)——前者筛掉不背书的,后者筛掉不理解的。

第三,”接口”是复杂性管理的真正落点。 EU-4 说复杂性只能被管理,四种手段是分解、抽象、接口、治理。接口是四者中唯一把”约定”变成”可执行、可测试、可追责”的那一个。 前置条件划清调用方的责任,后置条件划清实现方的责任,不变式划清双方的共同底线——契约写清楚了,集成时 90% 的扯皮就不会发生。本项目的 IDeviceAdapter 必须有明确的超时语义(超时算不算失败、失败要不要重试),这件事如果不在契约里写死,将来设备部和开发组会各执一词。

第四,为真实用户设计,不是为理想用户设计。 人机界面这块内容在所有教材里都排在最后、分值最低,但它恰恰是本项目上线成败的关键变量——操作工 46 岁、用了 12 年老系统,任何”更现代化的交互”对他们都是成本而不是收益。保留 F2/F4 快捷键、字号加大、30 秒撤销、双轨过渡一个月,这四条没有一条写在教科书的原则里,但它们是”用户控制权、减少记忆负担、一致性”这三条黄金规则在这个具体人群上的正确翻译。原则的正确性不体现在背诵上,体现在翻译上。

第五,不做微服务不是保守,是对组织能力的诚实。 拆分按业务能力还是按限界上下文,这个问题的答案取决于你的团队能不能承受分布式。我们选择了模块化单体——模块边界按限界上下文划定、模块间不共享表、全部通过接口与事件交互,但部署为一个应用。这样当下能跑得动(运维 2 人),将来要拆时是”拆部署”而不是”拆代码”。保留未来选项而不为未来买单,这就是推迟绑定时间这一可修改性战术的最高级形式。

下一周 S08 会问一个更扎心的问题:这套系统上线之后呢? 测试策略、质量保证、配置管理、维护类型——Q4(上线是终点还是起点) 将第一次被正面回答。而到 S11,当边缘计算、云原生进入视野时,S07 的”接口契约”会在”跨云、跨厂区、跨厂商”的场景下被重新检验一遍。


文章作者: v
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 v !
  目录