软件建模14:计划、资源与承诺
客户计划在十点至十二点租两台相机。系统能够接受这项请求,可能因为同型号还有两台容量,也可能因为已经选定设备 E1、E2。这两种确认都没有证明相机已经交付;交付时只拿走一台,也不能把原来承诺的两台悄悄改成一台。
账户、分录与费用 区分了费用、收款和记账。本篇继续使用合成租赁案例,把计划租期、资源承诺和实际执行分开,并运行两种资源分配候选。实验仍采用 UTC 半开区间、保留请求回执和取消不恢复旧请求的基线规则。同一个时间段为什么要出现三次
计划动作记录意图:希望什么类型的设备、多少台、何时使用。资源承诺记录系统已经接受的占用范围。实际动作记录显式登记的执行情况,包括实际数量和实际时段。三个对象可能恰好数值相同,也可能不同;相同值不会使它们成为同一个事实。
如果把计划直接当成占用,客户试填一张表就可能阻挡其他预订;如果确认后直接生成实际动作,未到场的客户会被当成已经领取设备。若只保留最后修改过的数量,则无法区分原来只承诺一台和承诺两台却只交付一台。这个区别决定取消、补交付和对账的后续规则。
本篇的原始模式线索来自 Fowler 站点托管、由 Pierre De Swert 整理的《UML Diagrams for Chapter 8 of Analysis Patterns》。图8.13将 Proposed Action 与资源分配的关系标为 books,将 Implemented Action 的关系标为 uses;图8.14区分一般与具体资源分配,分别关联 Asset Type 和 Asset。原始补充图8.13—8.14
原图还约束相关 Quantity 使用时间单位。本实验显式拆成整数设备台数和独立时段,是便于检查设备租赁的教学变体;不能把原图的 Quantity 直接解释为台数,更不能从一张关系图推导出所有取消与交付政策。
flowchart LR
P[ProposedAction 计划动作] -->|book 确认后| A[Allocation 资源承诺]
I[ImplementedAction 已登记实际动作] -->|引用原承诺| A
A --> G[General 候选]
A --> S[Specific 候选]
G --> T[AssetType 同类容量]
S --> E[Asset 指定设备]
E -->|属于| T
这张图描述实验对象关系,不是原图的完整重绘。代码保留 ProposedAction、Allocation、ImplementedAction 三个数据对象,用 ResourceBook 的两种模式比较分配算法;没有为了图中的两个候选再创建一套继承层次。
先按类型承诺容量
实验固定在2026年10月3日 UTC,时间只允许零点至二十四点的整数小时,区间必须非空且右端不含。CAM-A 类型最多同时承诺两台,PROJECTOR 类型最多一台。它们是固定配置,没有接入维修日历或动态扩容。
只构造一份数量为二、时段为 [10,12) 的计划 A,活跃分配仍为零。调用 book(A) 并获得确认后,活跃分配才出现;实际动作集合仍为空。代码同时检查这两个中间状态,避免用一个最终“成功”结果掩盖概念混用。
| 新请求 | 已存在的承诺 | 实验结果及理由 |
|---|---|---|
A,两台,[10,12) |
无 | 确认,占满该区间的两台容量 |
B,一台,[11,13) |
A | 拒绝,十一点至十二点需三台 |
C,两台,[12,13) |
A | 确认,十二点端点不重叠 |
数量冲突取决于同一时刻的占用峰值,不能把所有与新请求重叠的订单数量直接相加。另一个夹具先放入 L 的 [10,11) 一台和 R 的 [11,12) 一台,再申请 W 的 [10,12) 一台。L 与 R 分属不同小段,每段总数均为二,W 应当成功。
实现收集候选区间的起止边界及旧承诺落在其中的边界,排序后逐段检查。每段左端代表这一段的占用量,因为中间没有新的起止事件。这个夹具会揭露“找出全部重叠订单后一次求和”的错误算法;随后再申请 [10,11) 一台,则确实超过容量而被拒绝。
半开区间也决定了释放与再分配的边界。同一台设备在十二点结束旧承诺,可以从十二点开始新承诺;实验没有额外添加检查、清洁或运输缓冲。如果业务要求归还后留出一小时,这一小时必须进入资源约束,不能只写在页面提示里。把端点改成闭区间会改变相邻请求的判断,应当同时更改规则说明和反例。
类型级承诺回答的是固定容量模型下能否接受数量。它没有选出实物,也未证明今后一定能找到同时符合成色、附件、维修状态等条件的具体设备。若业务允许先按类型预订、稍后指定实物,两阶段之间还需要明确分配与失败处理规则,本实验没有实现这一转换。
指定设备后,冲突落到实物身份上
第二个候选固定 E1、E2 属于 CAM-A,P1 属于 PROJECTOR。每次请求必须给出具体设备,设备数等于承诺数量、不能重复、类型必须匹配。数量为二却传入两次 E1,不会被当成两台设备;申请相机却传入投影仪 P1,也会被拒绝。
E1 在 [10,12) 已被接受后,再申请它的 [11,13) 会失败。E2 的同一时段仍可成功,E1 的 [12,13) 也可成功。这里检查的是每个具体设备的占用,单纯知道 CAM-A 还有多少台并不足以判断指定 E1 的请求。
两种候选分别放在独立的 ResourceBook 实例中,不能合起来当成两份可出售库存。前一个候选比较类型容量,后一个比较给定实物;实验并不在它们之间搜索更便宜或更优的方案。即使当前两台相机同质,这也只是测试输入的简化条件。
类型与实物的关系还限制了替换含义。E1 换为 E2,在此夹具中仍满足 CAM-A;换为 P1 则连基本类型都不满足。即使类型相同,某份合同还可能固定附件或规格版本。前文类型对象保存的这些条件没有接入当前分配器,因此测试通过只证明输入中已声明的类型、身份、数量和时间规则。
取消释放承诺,回执保留过去结果
取消 A、C 后,新的两台请求 F 可以获得确认。但原来被拒绝的 B 使用旧编号重试,仍得到原拒绝结果。把成功后的 A 取消,再重放 A,也只读到原确认回执,活跃分配不会重新出现。确认回执描述过去这次请求的处理结果,不能用作当前资源仍被占用的查询结果。
这与系列基线保持同一身份语义:同编号同负载返回原结果,同编号改变时间、数量或设备则报错;成功与失败回执都保留。资源条件改变之后重新申请,应使用新编号。把回执集合当成活跃库存,会让已取消的请求继续占容量;把取消写成删除回执,则可能让迟到重试重新占用。
设备替换使用具体分配候选演练:先取消原先选择 E1 的 S1,再用新编号 S2 申请 E2。随后新请求能够占用 E1,旧 S1 的回执仍是确认。实验还先释放 E2 上的已有预留,确保替换目标确实可用。这几项查询共同证明替换后的资源占用与旧请求历史没有混在一起。
这里的替换是两次独立调用。取消成功而新申请失败时,原资源不会自动恢复;要保证业务上不可中断的换机,需要另行设计原子替换或补偿。本实验没有用一次顺利执行冒充这一保证。
部分履约不改写原承诺
请求 PART 承诺两台、[10,12),实际登记为一台、[10,11)。查询得到实际数量一和原承诺数量二,两条记录均保留。超额登记三台、登记到承诺时段之外,都会在这个教学入口被拒绝;真实业务如何记录违约交付,不能由这两条输入限制代替。
为了让实验边界可检查,本例规定每个承诺至多有一条实际动作,存在实际动作后禁止直接取消,部分履约仍保留全部原承诺。取消失败后再次查询,承诺仍在。这组规则是教学推演,不是图8.13或8.14给出的业务保证,也不意味着“实际只交一台,另一台就一定还能出租”。
保留全部承诺是保守且有限的选择:尚未实现后续分批交付、提前归还和剩余数量释放。要支持这些场景,需要决定哪些数量在哪个时刻解除义务,再表达成可检查的变化。直接把原数量从二改为一会同时抹掉差额,无法解释它是取消、未履约还是后来补交。
实际动作在程序中也只是一次明确输入,不是读取设备或签收系统得到的外部证据。该输入记录的是夹具声称发生的事情,断言证明程序如何保存和限制它;实验不能证明某位客户确实拿走了设备。把输入真实性与模型内部一致性分开,才不会因存在一条 ImplementedAction 就扩大验证结论。
运行两种候选
第14篇实验包 包含代码、模型和逐项结果。从解压后包含 `examples` 的目录运行:1 | |
入口只用 Python 标准库,记录的运行版本为3.14.4。错误输入由实验捕获并核对异常原因;未按预期拒绝、分配结果错误或状态不符都会导致非零退出。当前完整运行通过三十三项检查,退出码为零。
1 | |
完整输出在 examples/software-modeling/evidence/14/output.txt,规则边界在 examples/software-modeling/models/14/semantics.md。这些结果覆盖固定一天、整数小时、静态设备集合的单进程内存实验;没有并发竞争、持久化恢复、任意资源排程或全局最优保证。
本实验保留的三份信息可以分别回答:客户提出了什么、系统仍承诺什么、已经登记发生了什么。容量与具体设备只是两种不同的承诺依据。后续若把实际交付用于费用计算,仍需显式选择计费依据,不能从预留成功直接生成“已使用两台”的收费事实。






