软件建模08:分析模式的复用
设备 E1 交付时的累计运行读数是 120 小时,归还时是 126 小时。如果台账只有一个 operatingHours 字段,更新以后只能看到 126。需要计算本次使用量时,交付读数已经丢失。补一张读数记录表可以解决问题,但是否还要增加 Quantity、Measurement、Observation 等对象,需要由接下来的查询决定。
系列前七篇已用租赁场景检查结构、协作、行为和数值边界。分析模式提供的是其他问题中已经整理过的概念关系。本篇从两个候选实现比较开始,检验这些关系能否解释租赁读数,再用一个独立的合成身高观测例子检查可迁移部分。这里的设备读数是新增教学假设,尚未接入 Java 的 RentalDesk。
从丢失的答案确定问题
原有预留基线只处理指定设备、半开租期、重复请求和取消。它不读取运行小时数,也不会因为设备出现划痕而改变预留结果。新增需求是假设租赁交接需要保留读数和外观记录,并能回答“交付时登记了什么”。这个问题不能由当前设备状态完整回答。
先考虑更小的业务:操作员只查看设备最新累计小时数,不追溯、不结算,也没有第二类读数。一个固定字段已经足够。把它改成五张表以后,当前值查询反而要确定现象类型、筛选记录、比较时间。新增结构是否值得维护,应当有一个固定字段答不出来的问题作为理由。
实验把两种候选放在同一组输入上。候选 A 是设备编号到当前读数的字典;候选 B 是 Fact 列表,每项保存对象、现象、值、单位和发生时点。两者收到 E1 在 10 点的 120 小时读数时,当前答案相同。12 点再登记 126 小时后,A 只能返回 126,B 仍能按 10 点查询得到 120。
发生变化的是需要保留的事实粒度。若只有设备的当前属性,覆盖有明确含义;若查询指定交接时点,读数本身就要成为可保留的记录。这里先采用同一天的 UTC 整数小时简化时间,生产系统的时区、同刻多条记录和迟到登记都尚未进入这个实验。
原书概念与教学组合的距离
Fowler 的《Analysis Patterns》把分析中的可复用模型作为讨论对象。作者网站同时提供原书信息与补充材料入口;这些材料可以帮助定位概念,不能替租赁系统作出具体业务决定。书籍与补充材料入口
1999 年的第三章 UML 补充图区分量值、测量记录与类别观测:量值含数额和单位,测量记录把某个对象与被测现象连接起来,类别观测记录分类结果。这些结构分别见图 3.2、3.6、3.7。它们提供概念参照,本篇没有照搬原图,也没有声称教学代码完整实现原书。第三章 UML 补充图
租赁案例据此提出一个候选:设备是被观察对象,累计运行时间是现象,120 小时是量值;“外观有划痕”属于另一类事实。出租客户、预留请求和取消回执不会因此变成观测的子类。模式只覆盖适合它的问题,现有动作和约束仍保留自己的含义。
代码中的 Fact 是一个刻意简化的教学记录。它用字符串存值和单位,还不能在构造时阻止把“有划痕”放进运行小时数。这个缺口没有被漂亮类图消除。08 的实验只证明按对象、现象和时间保留记录能够回答新增查询;10 才实现量值与类别的校验分支。
用相同输入比较两种候选
从仓库根目录运行:
1 | |
实验使用 Python 标准库,无需数据库、网络或 Java 编译。全部检查使用显式条件和异常,不依赖可被优化开关关闭的 Python assert。每个场景输出输入所对应的实际答案、期待答案和 pass,失败会以非零状态结束。
| 场景变化 | 固定字段的答案 | 记录模型的答案 | 当前选择 |
|---|---|---|---|
| 只有一种当前读数 | 120 | 120 | 保留固定字段 |
| 新读数覆盖后查交付时点 | 126,不能还原交付值 | 120 | 保留历史记录 |
| 增加外观检查 | 原字段不能表达 | 按 condition 区分 | 增加带类型的记录 |
| 查询不存在的观察对象 | 需要单独处理键缺失 | 没有记录,返回 None | 仍需规定缺测语义 |
运行结果中的 single-current-field-agrees 表示两种候选在简单场景下得到相同答案。overwrite-loses-historical-answer 的实际值为 false,检查期待值也为 false:它证明覆盖方案不再保存 120,并非一次被忽略的失败。observation-preserves-checkout 则检查历史查询确实返回 120。
实验里还提供一个两条件的选择函数:需要历史或多种现象时选择观测,否则选择固定字段。这个函数是写明的教学决策表,不会从代码自动推断业务需求。它的意义在于让选择理由可以被修改和重跑,不能把函数返回的字符串当成模式适用性的客观评分。
换一个对象以后,哪些知识仍然成立
独立对照使用合成对象 P1 的身高记录:9 点登记 170 厘米,13 点登记 171 厘米,查询 10 点仍得到 170。这里没有真实患者,也不解释身高变化,更不作诊断判断。两个数值只是检验记录与查询结构的固定输入。
从 E1 换成 P1 后,保留的约束是“查询必须指定观察对象、现象和时间范围”。把所有记录放进同一列表仍应保证对象隔离,所以 P2 的查询返回空值。能够复用的是这组信息关系及反例,租赁的计费规则没有随对象名称一起迁移到临床场景。
量纲同样需要重新检查。60 分钟和 1 小时若只比较裸数字,会得到不相等;实验显式把分钟除以 60 后才得到相等。这个检查只证明该次换算的算术,没有提供可复用单位库。小时读数与身高不能相加,摄氏度的转换也不能直接套用这里的比例运算。
两个领域即使都保存 subject,身份管理仍可能不同。设备编号可以连接设备台账,临床对象标识会涉及另一套登记与访问规则。为避免把未知制度写进模型,本篇只检查字符串标识的隔离,不实现合并身份、撤销身份或访问授权。
模式复用以后仍要维护什么
采用历史记录后,新增成本会立即出现。同一设备同一现象同一时刻收到两条记录,应该保留两条、覆盖其中一条,还是要求人工选择?当前 latest 使用最大时点选择;同刻并列时依赖列表次序,没有解决冲突。这是实验的明确边界,不能拿一次正常结果证明记录模型已经适用于真实采集。
读数出错后也不能简单删除。若 126 后来被证明来自错误仪器,业务可能需要保留“曾登记过,但已否定”的信息。增加拒绝标记以前,观察历史只回答“列表里保存了什么”,还不能回答“当时相信哪条记录”。这项变化要求重新定义查询,而非仅在类图上增加一个布尔字段。
反方向的检查同样必要:取消预留不能表达成“设备被观察为未预留”。取消是改变当前占用的操作,还必须保留原请求回执。本章观测实验不验证预留取消契约;取消后的占用释放与回执重放由前几篇已有的 Java 行为实验承担。本篇不通过改名把这项契约移交给观测列表。
模式采用后的迁移也可以拆成可检查步骤。旧字段仍作为当前值显示,新收到的读数同时保存为事实记录;对同一批输入,两条查询应保持当前值一致。历史回填则必须标注来源,不能把数据库最后修改时间冒充交接发生时间。现有实验只进行了候选对照,没有执行双写或历史数据迁移,因此这是一项后续方案,不是运行结果。
评价这项变化时,最先检查的是新问题是否得到回答。字段数量增加、对象名称更接近书本,都不能代替交付读数仍可查询这一条判据。如果业务撤销追溯要求,只有单一当前值需要展示,固定字段依然是本章决策表认可的选择,不必为了保留模式名称继续维护整套历史。
候选模型可以先放在边界之外运行,等需求确定再决定是否接入。当前附件包含独立 labs/08/check.py 和语义说明,没有修改 rental-core。这样可以复查新概念解释了哪些输入,也不会让读者误以为已有预留 API 已经支持交接、读数采集或结算。
附件与复查入口
本次运行输出以 PASS 08: 10 scenarios 结束,原始输出保存在 examples/software-modeling/evidence/08/output.txt,命令、环境和退出码在同目录。它验证的是固定样本和决策表,不是业务访谈结果、性能比较或通用模式推荐器。
与基础视角的衔接见 软件建模05:UML 类图与交互图;整个案例的已实现边界见 软件建模00:模型帮助作出什么决定。






