简介《大象-Thinking in UML》配套光盘面向UML初学者、软件建模实践者及高校相关课程学习者用于配合书中案例理解面向对象分析与设计流程。压缩包共1903个文件、约9.25MB以htm网页、tif/png/jpg等插图、cnt目录文件为主并含jar、pdf、mdl与db等配套文件收录彩色插图、Rose建模原文件、HTML版建模示例、Java虚拟机及《OO系统分析员之路》PDF。读者可用Rose原文件还原建模全过程观察各阶段模型的组织方式未装Rose时可通过HTML版在浏览器查阅同一示例并借助Java虚拟机解决显示问题。彩色插图按书中编号命名便于对照细节PDF文章则补充面向对象系统分析员的学习路线。已有573人学习下载适合希望把UML理论落到建模实践中的读者参考。1. 《大象-Thinking in UML》配套光盘下载之后先分清它是资料盘还是安装盘《大象-Thinking in UML》配套光盘下载下来之后很多人的第一反应是双击根目录那个自动播放菜单然后对着一屏十几年前风格的界面发呆。这份资源的本质不是软件安装包而是一整本 UML 教材的副产物全书示例的模型工程文件、按章节拆开的电子课件、案例素材图可能还夹着建模工具的试用安装包和几段建库脚本。它解决的是“不想从空白画布开始画图”这个问题——用例图、类图、时序图、活动图都已有成品把里面的名词换成自己项目的比从头画省太多事。适合三类人正在系统学 UML 建模的学生、要给需求文档和设计文档配图的后端工程师、以及需要一套完整建模案例撑起课程设计的人。先把“这是资料盘不是安装盘”这件事想明白后面的操作才不会走偏。2. 拆开光盘目录模型文件、课件与工具包的三层结构光盘镜像常见两种形态一种是单个 ISO挂载或解压后得到一棵目录树另一种是别人转存过的散装文件夹里面套着“新建文件夹(2)”这种命名。不管哪种拿到手第一件事都不是打开某个模型文件而是把目录结构摸清楚。这类配套盘一般按章节编号组织ch01到ch12各一份每种资源一个平行子目录模型工程、幻灯片、案例源码、工具安装包、说明文件。老光盘的命名相当随意中文路径、空格、全角括号混着来有的还把 Readme 存成 GBK 编码。先做体检再开图是省时间的做法。2.1 先给整张盘做一次文件指纹体检体检要解决三个问题盘里到底有多少文件、哪些是重复转存的、解压有没有缺文件。哈希值就是那个“后悔药”——后面用建模工具打开再保存文件被工具悄悄升级成新格式时你还能拿出原始哈希对照确认哪些被动过。import csv, hashlib, os, sys def sha256_of(path, chunk1024 * 1024): 分块读取计算哈希避免一次性把大文件读进内存 h hashlib.sha256() with open(path, rb) as f: for block in iter(lambda: f.read(chunk), b): h.update(block) return h.hexdigest() rows [] root sys.argv[1] # 光盘挂载点或解压后的根目录 for dirpath, _, files in os.walk(root): for name in files: full os.path.join(dirpath, name) try: size os.path.getsize(full) except OSError: continue # 坏扇区或权限问题跳过而不是中断 rows.append({ rel_path: os.path.relpath(full, root), ext: os.path.splitext(name)[1].lower(), size_kb: round(size / 1024, 1), sha256: sha256_of(full) if size 200 * 1024 * 1024 else , }) with open(manifest.csv, w, newline, encodingutf-8-sig) as f: w csv.DictWriter(f, fieldnames[rel_path, ext, size_kb, sha256]) w.writeheader() w.writerows(rows) print(len(rows), files indexed)几个参数值得说一下。chunk取 1MB 是磁盘 IO 和内存占用的折中光盘上全是几 MB 的模型文件再大没有意义。size 200 * 1024 * 1024这个阈值是给工具安装包留的余地——超过 200MB 的基本是安装程序不需要算哈希省一半时间。写文件时用utf-8-sig而不是utf-8是因为这份 CSV 你大概率要用表格软件打开核对带 BOM 才不会让中文路径变成乱码。rel_path用相对路径而不是绝对路径是为了后面把这份清单直接提交进版本库换台机器也能复现。跑完之后先看总行数。如果解压出来的文件数和光盘说明里写的对不上八成是解压中断或者镜像本身有问题这时候别急着往下走先重新挂载。2.2 后缀对照表哪个文件该用哪个工具接模型工程文件是这张盘里最值钱的部分也是最容易打不开的部分。老教材配套的工程文件后缀五花八门同一个知识点在不同章节可能用了不同工具存。先把后缀和工具对上号再决定用哪条路打开。后缀常见来源能否直接打开处理办法.mdl早期商业建模工具的原生工程需要同族工具且版本不能差太远先只读备份再考虑导出交换格式.vpp另一款商业建模工具工程高版本工具通常能读低版本用新版打开后立刻另存别在原文件上保存.zargo开源建模器工程本质是压缩包需要同族工具直接解压内部一般有 XMI.oom老一代对象建模工具现代系统上基本打不开只当参考靠截图对照重画.xmi通用模型交换格式多数建模器可导入首选的中转格式.uml文本化的模型描述纯文本编辑器就能读直接进版本库最好管.drawio/.vsdx手绘示意图、文档内嵌图通用绘图工具当素材用不做结构复用.ppt/.doc电子课件、实验指导办公套件注意缺字体导致的排版错位.sql案例库建库脚本数据库客户端建库后跑一遍验证案例数据完整一个容易忽略的点.zargo这类后缀看着像私有格式其实就是个 zip把后缀改成.zip解压里面经常直接躺着 XMI 文件。这一步能把“需要装一个十几年前的工具”变成“用现成工具导入 XMI”。反过来.mdl是二进制私有格式除了原厂工具没有可靠解法别指望文本编辑器能救。2.3 按扩展名做一份体量表判断“这盘值不值得留”光盘动不动几百 MB但真正有复用价值的往往只有几 MB。做个体量分布能帮你决定哪些目录进版本库、哪些直接丢掉。import collections, csv rows list(csv.DictReader(open(manifest.csv, encodingutf-8-sig))) count collections.Counter() weight collections.Counter() for r in rows: ext r[ext] or (no ext) count[ext] 1 weight[ext] float(r[size_kb] or 0) for ext, n in count.most_common(): print(f{ext:10s} {n:5d} 个 {weight[ext] / 1024:8.1f} MB) # 同一份文件被拷了多份按哈希分组超过一条就是重复 dup collections.defaultdict(list) for r in rows: if r[sha256]: dup[r[sha256]].append(r[rel_path]) for h, paths in dup.items(): if len(paths) 1: print(重复:, paths)这段的输出通常长这样.bmp或.jpg数量最多、体积最大.mdl、.xmi数量少但排在价值第一位工具安装包单独占一大块。我的习惯是把模型工程、.sql、.uml单独抽到一个keep/目录课件按章节留安装包和示例图片全部不进库——图片占了八成体积真正有用的信息在图里不在文件本身。重复检测也要看一眼。同一份ch05.mdl出现在Models/和Backup/两个地方很常见这时候保留带修改时间更晚的那份另一份删掉后面做批量处理时就不用纠结处理的是哪个副本。3. 打开十几年前的 UML 模型低版本兼容、XMI 中转与文本化三条路三条路的取舍很清楚只是想看看图长什么样走第一条要把图改成自己项目的走第二条要把模型纳入版本管理长期维护走第三条。别一上来就奔着第三条去转文本是有信息损失的图形布局基本保不住先确认原图长什么样再动手。3.1 路径一只读副本 低版本兼容打开第一个动作永远是复制一份把副本设成只读。老建模工具有个通病打开工程时不提示就按自己理解的格式重写一遍保存之后原文件就回不去了。只读属性至少在保存时会弹个窗给你一次反悔的机会。在 Windows 上给整个目录加只读可以直接用命令行比右键属性一个个点快得多# 在解压后的资源根目录执行把模型工程整体设为只读 attrib R models\\*.* /S # 需要放开时 attrib -R models\\*.* /S/S表示递归子目录R是加只读、-R是去掉。这一步做完用资源管理器看文件图标会带个小锁心里就有底了。环境层面老工具常见的三个坎一是 32 位运行时依赖新系统上装完启动报缺少某个运行库二是 Java 运行时版本需要特定主版本号才肯启动版本太新反而起不来三是安装目录不能带中文和空格。第三条最容易被忽视装在D:\建模工具\下面启动失败换到D:\tools\modeler\就好了这种玄学问题排查起来能耗掉一晚上。稳妥做法是把工具装进一台隔离的虚拟机快照留一份装崩了直接回滚。打开之后先别动图把每个.mdl过一遍记下哪些能正常渲染、哪些报错。报错的单独列出来走第二条路。3.2 路径二XMI 中转的导出参数与导入顺序XMI 是 OMG 定的模型交换格式几乎所有建模工具都支持导入导出是跨工具唯一靠谱的桥。但导出时的勾选项决定了你导出来的是“一张图”还是“一堆没有位置的元素”。导出选项建议值不这样设的后果XMI 版本2.1其次 2.4.1老工具多半只认到 2.1选高了导入端直接拒收图形布局Diagram Interchange勾选不勾只剩语义导进去是一堆散点得手工重排构造型与标记值勾选«include»、«extend»这类关系会退化成普通关联文本编码UTF-8中文用例名变成问号改都没法改ID 冲突处理导入时新建 GUID直接合并会覆盖同工程里的既有元素操作顺序也有讲究别反过来在能打开原工程的那台机器上逐个工程导出 XMI一个工程一个文件文件名带上章节号和原工程名。导出完立刻用文本编辑器打开其中一个搜一下中文标签确认不是问号或#26041;这种实体乱码。在目标工具里先建一个空工程用“导入”而不是“打开”导入时选新建 GUID。导入后先数元素类数量、关联数量、图数量和原工具里显示的对照对不上就说明有元素没导过来。最后再打开图看布局布局丢了就接受现实语义对了就够用。第 4 步的核对最容易被跳过但它是唯一能在十分钟内判断导入是否完整的办法。数目对不上八成是 XMI 版本选高了或者导出时没勾构造型。3.3 路径三把类图抽成文本扔进版本库图形布局在文本化过程中必然丢失但类名、属性、关联这些语义结构能完整保留而且文本能 diff、能 code review、能跟着代码一起演进。要把 XMI 里的类图抽出来靠标准库就够import csv import xml.etree.ElementTree as ET UML {http://www.omg.org/spec/UML/20131001} XMI {http://www.omg.org/spec/XMI/20131001} root ET.parse(ch06-class.xmi).getroot() # 第一遍把 xmi:id 映射到类名后面解析关联要用 id2name {} for el in root.iter(UML Class): name el.get(name) if name: id2name[el.get(XMI id)] name # 第二遍关联两端的类型过滤掉解析不到名字的 relations set() for assoc in root.iter(UML Association): ends [] for prop in assoc.iter(UML Property): ends.append(id2name.get(prop.get(type))) ends [e for e in ends if e] if len(ends) 2 and ends[0] ! ends[1]: relations.add(tuple(sorted(ends))) with open(classes.csv, w, newline, encodingutf-8-sig) as f: csv.writer(f).writerows([[class]] [[n] for n in sorted(id2name.values())]) with open(relations.csv, w, newline, encodingutf-8-sig) as f: csv.writer(f).writerows([[source, target]] [list(r) for r in sorted(relations)]) print(len(id2name), classes,, len(relations), associations)关键在命名空间。XMI 2.x 的元素都挂在 OMG 的 UML 命名空间下不同工具导出的命名空间字符串可能带年份差异20131001是最常见的一版如果root.iter()一个类都没找到先把根节点的xmlns打出来看看实际值改一下常量就行别怀疑代码逻辑。xmi:id是元素唯一标识关联边不直接存类名而是存两端的 id 引用所以必须先建映射表再解析边。关联去重用了sorted加set因为无向关联在 XMI 里两端顺序不固定不去重会出现同一条边正反各一条。导出的两个 CSV 直接提交进版本库配合一段脚本生成类清单比提交一堆二进制工程文件友好得多。类图和关联图能这样处理用例图稍微麻烦点——参与者和用例之间靠关联连但«include»是带构造型的依赖抽取时要把构造型名一起带上否则重构完就分不清谁包含谁了。4. 把示例图改成自己的图用例、类图、时序图的复用与重命名光盘里的示例基本都是电商、图书管理、教务系统这类经典题目改起来工作量不大但改错的地方很隐蔽图改了、文档没改或者图里的类改了、时序图里的消息名没跟着变。复用的正确姿势是先建映射表所有改动从一个地方出发。4.1 用例图与类图先列名词映射表再批量替换映射表就是两列 CSV旧名称、新名称。旧名称按原图里的原文抄一个字符都别改包括全角括号和空格。新名称用你自己项目的术语同一概念在全盘保持唯一写法——“订单”和“定单”混用是后面所有对齐问题的根源。import csv, pathlib, re # rename.csv 两列old,new先用表格软件核对一遍再跑 mapping dict(csv.reader(open(rename.csv, encodingutf-8-sig))) # 长词优先避免“订单明细”被“订单”先替换掉一半 pattern re.compile(|.join(re.escape(k) for k in sorted(mapping, keylen, reverseTrue))) target pathlib.Path(out/xmi) for f in target.glob(*.xmi): text f.read_text(encodingutf-8, errorsreplace) new pattern.sub(lambda m: mapping[m.group(0)], text) if new ! text: f.write_text(new, encodingutf-8) print(updated, f.name)三个细节决定这段代码靠不靠谱。sorted(..., keylen, reverseTrue)是必须的不做长词优先“订单明细”会被替换成“采购单明细”这种半截结果而且替换顺序还依赖字典顺序不稳定。errorsreplace是防御性的老文件的编码声明经常和实际字节对不上遇到坏字节直接抛异常会中断整批处理替换成占位符至少能跑完跑完再回头查哪些文件有问题。只处理.xmi而不碰.mdl是因为二进制文件做文本替换必坏。替换完别直接信结果回到工具里打开图重点看三处关联线两端有没有断、构造型有没有被误伤、注释框里的文字改没改。构造型里带尖括号的部分比如«include»里的 include如果在映射表里被列进去了也会一起被换掉这是最容易翻车的地方改之前先把尖括号内的词从映射表里剔掉。4.2 时序图与活动图编号连续性和泳道标题时序图是复用难度最高的图因为它的信息密度全在顺序上。示例里的消息编号常写成1、1.1、1.2、2这种层级结构你删掉一条分支后面的编号全断读者顺着编号找就对不上了。改完先跑一遍连续性检查比人眼扫强import re, sys text open(sys.argv[1], encodingutf-8).read() nums re.findall(r^\s*(\d(?:\.\d)*)\s, text, re.M) def key(n): return tuple(int(x) for x in n.split(.)) prev None for n in nums: k key(n) if prev is not None: same_level len(k) len(prev) and k[:-1] prev[:-1] and k[-1] prev[-1] 1 deeper len(k) len(prev) 1 and k[:-1] prev if not (same_level or deeper): print(断号:, ..join(map(str, prev)), -, n) prev k逻辑很简单相邻两条消息要么是同级递增1.1到1.2要么是进入下一层1到1.1其余都算断号。re.M让^匹配每一行的开头因为编号一般在消息行首。这个脚本只查断号不查重号重号在时序图里更常见——复制一段分支忘了改编号扫一眼输出里有没有重复数字即可。活动图的坑在泳道。泳道标题是独立图元导出图片时经常被裁到画布外尤其是有三条以上泳道、画布宽度不够的时候。稳妥做法是导出前把画布调宽一档或者干脆导出 SVG让排版工具去管边界。4.3 导出与命名分辨率、格式、目录约定图最终是要进 Word 或在线文档的导出参数没设好前面改得多漂亮都白搭。位图在文档里被放大就糊矢量图不会所以优先级是 SVG 高于 PNG。用途格式关键参数在线文档、MarkdownSVG不设分辨率矢量无限缩放Word、PDF需要位图PNG按最终显示宽度的 2 倍导出一般 300dpi 起步打印、答辩 PPTPNG 或 EMF300dpi白底不透明存档源工程 SVG两个都留源工程以后还要改命名统一成章节号-图类型-序号-主题全小写、空格换下划线。批量改名用脚本别手改#!/usr/bin/env bash set -euo pipefail srcexport/png dstdocs/figures mkdir -p $dst i0 for f in $src/*.png; do [ -e $f ] || continue i$((i1)) base$(basename $f .png) slug$(printf %s $base | tr A-Z a-z | tr _) printf -v n %02d $i cp $f $dst/ch03-usecase-${n}-${slug}.png done echo renamed $i filesset -euo pipefail让脚本遇到错误立刻停不会默默产出半套文件。printf -v n %02d补零是为了文件名排序和文档里的图号顺序一致序号不补零的话10会排在2前面。cp而不是mv是为了保留原始导出文件万一命名规则要改还能重跑。5. 五种翻车现场乱码、布局丢失、文件被覆盖怎么排查5.1 打开全是方块和问号现象模型文件能打开但里面的中文标签全部显示成方块或者一串问号。原因有两个层面。一是文件编码老工具默认按本地编码写文件换到 UTF-8 环境里就解不出来二是字体缺失光盘里自带的字体没装工具用默认字体渲染中文就是方块。两者表现很像区分办法是看英文标签正不正常——英文正常而中文全乱是编码连英文都显示成方块是字体。解决先确认编码。用文本编辑器打开对应的.uml或.xmi看文件头有没有encodingUTF-8声明如果声明是 UTF-8 但内容确实是乱码说明源文件本身就是本地编码存的用编辑器的“重新以 GBK 打开”试一次能看到正常中文就另存为 UTF-8。字体问题更好办把光盘里fonts/目录下的字体装进系统重启工具即可。5.2 双击光盘菜单毫无反应现象挂载镜像后双击根目录的启动程序没有任何反应或者一闪而过。原因这套菜单是十几年前做的依赖的播放器和运行库在新系统上根本不存在有些还是 16 位程序64 位系统直接不执行。杀软也可能把它当可疑行为拦下来。解决别跟菜单较劲直接浏览目录。菜单的唯一作用是给你一个索引而索引在Readme或目录名里本来就有。真要还原菜单只能在虚拟机里装老系统为这点收益不值得。跳过菜单之后按第 2 章那份后缀对照表挨个目录看效率反而更高。5.3 导入 XMI 后图变成一堆散点现象XMI 导入成功元素数量也对但打开图一看所有类挤在左上角连线乱七八糟。原因导出时没勾图形布局或者勾了但导入端不认这套布局扩展。布局信息存在 Diagram Interchange 扩展节点里属于 XMI 的可选部分各家实现差异很大。解决先确认导出端有没有勾上布局勾了还是不认就别修布局了直接按语义重排。重排也有省力办法——先把类按包分组同包的类放在一起再连边比无脑拖动快得多。核心结论是XMI 保证语义不保证布局把它当中转语义的桥不要当中转版式的桥。5.4 原文件被工具“升级”覆盖现象打开工程时提示“此文件由旧版本创建是否升级”点了是保存之后原来的工具再也打不开。原因格式升级是单向的新版工具保存出来的文件低版本读不了。有些工具还不提示直接按新格式写回去。解决这事没有后悔药只能事前防。按 3.1 的做法设只读或者干脆在复制出来的副本上操作原始文件只当存档。更进一步把 2.1 生成的manifest.csv一起留档哪天怀疑文件被动过重新算一遍哈希一比对就知道。养成“打开前先复制、保存前先改名”的习惯这类问题基本不会再有。5.5 图插进文档糊成马赛克现象SVG 插进 Word 显示异常换成 PNG 之后放大又糊。原因Word 对 SVG 的支持视版本而定老版本直接不认位图糊是因为导出分辨率低于最终显示尺寸。这两个问题经常一起出现让人以为是图本身的问题。解决位图按“最终显示宽度的 2 倍”导出A4 纸正文宽度约 16cm图占一半宽度时导出宽度不低于 1600px 就够清晰。表里那些 300dpi 的说法落到操作上就是这个意思。优先选 SVG实在不兼容再退回高分辨率 PNG别用截图工具截屏——截图的分辨率永远等于你屏幕的分辨率插到文档里必然糊。6. 交付前的三分钟验收一张检查表和一套留档习惯图改完不算完交付前得有一道固定关卡否则返工成本比画图还高。我自己的检查表就四项每项都能在一分钟内过完。检查项合格标准不合格的典型表现语义对齐图里的类名在文档正文里都能搜到图改了文档没改读者对不上号编号连续时序图消息编号无断号无重号删了分支没重排出现1.1直接跳1.3导出完整每张源工程都有对应的 SVG只有源文件没导出图别人打不开命名规范文件名小写、无空格、带章节号一堆“未命名-1.png”堆在目录里四项里最容易漏的是第三项。源工程在自己的机器上好好的发给别人打开是空白因为对方没装那个建模工具。交付物里必须同时有源文件和导出图缺一不可。第四项可以交给脚本省掉人眼扫目录的时间import csv, os, sys rows list(csv.DictReader(open(manifest.csv, encodingutf-8-sig))) problems [] # 命名规范文件名里不该出现空格和井号 for r in rows: name os.path.basename(r[rel_path]) if in name or # in name: problems.append(f命名不规范: {r[rel_path]}) # 交付完整每份源工程都要有同名的导出图 for r in rows: if r[ext] in (.xmi, .uml, .mdl): stem os.path.splitext(r[rel_path])[0] hit [x for x in rows if x[rel_path].startswith(stem) and x[ext] in (.svg, .png)] if not hit: problems.append(f缺导出图: {r[rel_path]}) print(\n.join(problems) if problems else 全部通过)这段的前提是每次改动后重新生成一遍manifest.csv否则查的是旧清单等于没查。startswith(stem)匹配的是同名不同后缀所以导出图的命名要和源工程保持一致这也是 4.3 那套命名规则存在的理由——命名不统一自动化检查就无从谈起。还有一个进阶用法把manifest.csv和源工程一起提交进版本库每次交付前跑一遍哈希比对能立刻发现“谁把原图覆盖了”。这套做法平时看不出价值出一次事故就回本了。血泪经验是我曾经把一批改好的图直接从工具里“另存为”覆盖了原始工程结果第二天发现有一版改错了方向想退回原图对照硬盘上已经只剩改过的版本。从那以后我每次动模型文件之前都强制走一遍“复制到 work 目录、原件设只读、哈希入清单”这三步慢是慢了点但再没为找回原图花过时间。希望帮到你。本文还有配套的精品资源点击获取