使用Graphviz绘画UML图

本文使用TeXmacs中的Graphviz插件,对Milo Yip的《使用Graphviz绘画UML图》做了重新排版。

展开查看详情

1. 使用Graphviz绘画UML图 by Milo Yip 本文的作者是 Milo Yip , Darcy Shen 于 2020 年 4月 4日对本文的内容做了重新排版, 以测试刚刚 在 Xmacs 中研发的Graphviz 插件。 欢迎大家关注 Xmacs微信公众号。 1 类图 UML 类图(class diagram)是最常见的图, 用于 表示系统的静态结构。 UML中类以矩形表示。 我 们可以在 dot 文件中预设节点的形状, 并且设置 一些如字体等属性: digraph G { node [shape=box, fontsize=10, penwidth=0.5 ] Foo Bar } Foo Bar 图 1. 类图

2.稍后我们再谈如何加入类的成员。 2 继承 继承( inheritance )是类之间很重要的关系, 在 UML 中又称其为泛化(generalization)关系, 以 空心箭头表示派生类指向基类。 在 DOT 语言中, 可以设置边的箭头形状, 不过要注意, 通常我们 会把基类放在上面,因此我通常会这样设置: digraph { node [shape=box, fontsize=10, penwidth=0.5] Animal, Mammal, Reptile, Dog, Cat, Snake /* inheritance */ { edge [arrowtail=onormal, dir=back] Animal -> { Mammal, Reptile } Mammal -> { Dog, Cat} Reptile -> Snake } }

3. Animal Mammal Reptile Dog Cat Snake 图 2. 3 关联 UML 中的关联(association)描述两个类的关系, 以类之间的实线表示。 例如人和杂志的关系是订 阅: digraph { node [shape=box, fontsize=10, penwidth=0.5] Person, Magazine /* Association */ { edge [dir=none]

4. Person -> Magazine [label=" subscribe"] } } Person subscribe Magazine 图 3. 关联 我们经常会表示关联之间的多重性( multiplicity),例如Person类的实例最多可订阅 5 本杂志,而每本杂志可被任意数目的人订阅: digraph { node [shape=box, fontsize=10, penwidth=0.5] edge [fontsize=10, penwidth=0.5] Person, Magazine /* Association with multiplicity */ { edge [dir=none]

5. Person -> Magazine [ label=" subscribe", headlabel="0..10", taillabel="* " ] } } Person * subscribe 0..10 Magazine 图 4. 注意,上面的例子在label、headlabel、taillabel加 入空格避免它们太贴近连线(这不完美)。 关联可以是单向或双向的, 以线形箭头表示, 无 箭头也表示双向关联。以下展示单向关联,面试官 知道他对应的候选人,但候选人不知道面试官: digraph { node [shape=box, fontsize=10,

6. penwidth=0.5] edge [fontsize=10, penwidth=0.5] Interviewer, Candidate /* Unidirection association */ { Interviewer -> Candidate [ arrowhead=vee ] } } Interviewer Candidate 图 5. 4 聚合 聚合(aggregation)是一种特殊的关系,是一种弱 的包含关系, 包含方以空心菱形表示。 例如, 一

7.个部门含有一些员工: digraph { node [shape=box, fontsize=10, penwidth=0.5] edge [fontsize=10, penwidth=0.5] Department, Employee /* Aggregation */ { edge [dir=back, arrowtail=odiamond, headlabel="* "] Department -> Employee } } Department * Employee 图 6.

8.5 组成 组成(composition)是更强的包含关系, 说明一 个类的实例是另一个类的组成部分,它们有一致的 生命周期, 组成方以实心菱形表示。 例如, 一家 公司由多个部门组成, 若果公司结业, 部门也不 存在了: digraph { node [shape=box, fontsize=10, penwidth=0.5] edge [fontsize=10, penwidth=0.5] Company, Department, Employeee // Composition { edge [dir=back, arrowtail=diamond, headlabel="* "] Company -> Department } // Aggregation { edge [dir=back, arrowtail=odiamond,

9. headlabel="* "] Department -> Employeee } } Company * Department * Employeee 图 7. 6 依赖 依赖(depedency)关系说明一个类会使用到另一 个类,例如表示以一个类作为成员方法的参数或返 回值。UML 中采用线形箭头和虚线表示。以下的 例子表示工厂创建产品,常见于各种工厂模式,工

10.厂不拥有产品。 digraph { node [shape=box, fontsize=10, penwidth=0.5] edge [fontsize=10, penwidth=0.5] Factory, Product // Dependency { edge [arrowhead=vee, style=dashed] Factory -> Product [ label=" <<create>>" ] } } Factory <<create>> Product 图 8.

11.7 类成员 类除了名字, 也可以展示其成员。 成员包括属性 (attribute)和方法(method)。 每个成员的可见性(visibility)以一个前置符号表 示: + . 公有(public) - . 私有(private) # . 保护(protected) ~ . 包(package) 如果成员为静态(static)的,则加下划线。 属性的格式为: <visibility> <attribute name> : <type> 方法的格式为: <visibility> <method name> (<param1 name> : <param1 type>, ...) : <return type>

12.Graphviz 可使用 record shape 或 HTML table 来 分隔类名字、属性和方法, 例如以下的 C++ 类: class Account { public: void Deposite(int amount); void Withdraw(int amount); int GetAmount(); protected: int balance; private: string owner; }; 用 record shape 的话可写作: digraph { node [shape=record, fontsize=10, penwidth=0.5] Account [label="{ Account | # balance : int\l - owner : string\l | + Deposite(amount : int)\l

13. + Withdraw(amount : int)\l + GetBalance() : int\l }"] } Account # balance : int - owner : string + Deposite(amount : int) + Withdraw(amount : int) + GetBalance() : int 图 9. 当中,\l是代表该行向左对齐并换行。 如需更多控制,则可使用 HTML table,但就会更 冗长: digraph { node [shape=plaintext, fontsize=10, penwidth=0.5] Account [label=< <table border="0" cellborder="1" cellspacing="0">

14. <tr><td align="left" balign="left"> # balance : int<br/> - owner : string </td></tr> <tr><td>Account</td></tr> <tr><td align="left" balign="left"> + Deposite(amount : int)<br/> + Withdraw(amount : int)<br/> + GetBalance() : int<br/> </td></tr> </table> >] } # balance : int - owner : string Account + Deposite(amount : int) + Withdraw(amount : int) + GetBalance() : int 图 10. 使用 HTML table 可加入 <U></U> (下划线 )、 <I></I>(斜体)等字体控制, 但只在一些

15.渲染器中有效。 如需表示静态或抽像, 可利用 stereotype <<abstract>>、<<static>> 等说明。 再重申一次, 类图不必要展示所有细节, 可按想 表达的意思仅加入部分成员,每个方法也可忽略一 些参数细节。 8 包 在比较大的系统里, 类通常会用包(package)的 方式来组织。Graphviz 不能简单还完 UML 包的图 形,但可以使用 subgraph cluster 功能去近似地表 示类属于那个包。 例如: digraph { graph [fontsize=10, penwidth=0.5, labeljust=left] node [shape=box, fontsize=10, penwidth=0.5] edge [fontsize=10, penwidth=0.5] subgraph clusterView { label="View" AccountView, CustomerView }

16. subgraph clusterModel { label="Model" Account, Customer } /* Unidirecitonal association */ { edge [arrowhead=vee] AccountView -> Account CustomerView -> Customer } } View CustomerView AccountView Model Customer Account 图 11. 注意,subgraph的名字必须以cluster为前缀。

17.9 排布技巧 自动排布故然很方便,但有时候我们想做出一些修 改。 例如, dot 描述的是有向图, 从来源节点指 向目标节点时, 目标节点就会成为下一级, 预设 设置下,节点会垂直排列,如以下例子: digraph { node [shape=box, fontsize=10, penwidth=0.5] Canvas, Shape, Rectangle, Circle // Inheritance { edge [arrowtail=onormal, dir=back] Shape -> { Rectangle, Circle } } // Composition { edge [dir=back, arrowtail=diamond, headlabel="* "] Canvas -> Shape } }

18. Canvas * Shape Rectangle Circle 图 12. 但有时候我们想作一些改动, 例如继承沿用 这种方式, 但关联时则以水平。 我们可以使 用rank=same去设置一组节点为同一级,节点之间 的距离可整体设置nodesep属性: digraph { graph [nodesep=1] node [shape=box, fontsize=10, penwidth=0.5] { rank=same Canvas, Shape }

19. Rectangle, Circle // inheritance { edge [arrowtail=onormal, dir=back] Shape -> { Rectangle, Circle } } // composition { edge [dir=back, arrowtail=diamond, headlabel="* "] Canvas -> Shape } } Canvas * Shape Rectangle Circle 图 13.

20.10 颜色 UML 图也不一定是黑白的。做软件设计时可以加 入颜色去加入一些意思,例如不同包的类可设置为 不同颜色。 挑选颜色是一个头痛的问题, 可以采 用 Graphviz 的配色方案(color scheme)功能。例 如用colorscheme=spectral7设置 7 个光谱色配色 方案, 然后我们可以用fillcolor=1至7去填充节 点形状: digraph { graph [fontsize=10, penwidth=0.5, labeljust=left] node [shape=box, fontsize=10, penwidth=0.5, style=filled, colorscheme=spectral7] edge [fontsize=10, penwidth=0.5] subgraph clusterView { label="View" node [fillcolor=4] AccountView, CustomerView } subgraph clusterModel { label="Model" node [fillcolor=6] Account, Customer }

21. /* Unidirecitonal association */ { edge [arrowhead=vee] AccountView -> Account CustomerView -> Customer } } View CustomerView AccountView Model Customer Account 图 14. 配色方案