You are on page 1of 44

Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

苹果乔布斯的名言:“创造无非就是把事物联系起来”。
人们怎样才能更好地建立这类联系呢?
乔布斯又说:“你不可能在眺望未来时把生活中的每个点连接起来,
只有回顾时能才连点成线 。

第 3章
30 分钟认识 UML 顺序图

3.1 绘制 UML 顺序图(Sequence Diagram)


启动了 Astah,在主画面上点选< Diagram>,出现:

38
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接着,点选<Sequence Diagram>,就会出现一张空白的顺序图,如下:

在空白的顺序图上方,有一排顺序图的元素(Element),简称「图素」,如下:

此列元素中的左边第 2 个就是「生命线」(Lifeline)图素,表达出一个对象
(或类别)生命周期里攸关事件(Event)的发生顺序线,简称生命线。当你点选此
图素(如上图所示),然后移动鼠标(Cursor)到图表上,按下鼠标键,就出现一
个生命线图素,如下:

39
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这让你输入对象(或类别)的名称,例如输入名称:Activity, 如下:

同样地,还可以再拉出更多的生命线,如下:

40
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

当某个对象触发了事件(Event),发出讯息(Message),可能会引发另一个
对象的事件。UML 顺序图能表达这种事件的发生和连动顺序,所以又称为事
件追踪(Event-Trace)图。由于其形状很像栅栏,又俗称为栅栏图。
你可以在图形上表达上述的讯息传递关系。例如,选取<Message>图素,
如下:

先点选了这个图素,接着将鼠标移动到 Activity 生命线上,按住并拖拉


到 myActivity 生命线才放开,就出现:

41
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

就可以填入讯息名称了;例如 onCreate(),如下:

这表示了,一开始由 Activity 发出讯息给 myActivity。这个讯息触发了


myActivity 的 事 件 去 执 行 onCreate() 函 数 。 此 函 数 执 行 时 又 发 出 讯 息 给
Activity,引发 Activity 的事件去执行 setContentView()函数。在顺序图里,表
达如下:

42
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

如果对照到大家所熟悉的程序码:

public class myActivity extends Activity implements OnClickListener {


// ……..
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// ……..
// ……..
setContentView(layout);
}

上图就表达了这段程序码里的:
z Activity 调用 myActivity 的 onCreate()函数。
z myActivity 执 行 onCreate()函 数 时 , 反 过 来 调 用 Activity 的
setContentView()函数。

3.2 <诞生讯息>(Create Message)


如果在执行 onCreate()函数时,会发出讯息给其它对象或类别,UML 顺
序图可以表达的更详细的讯息传递。例如,下述的详细程序码:

public class myActivity extends Activity implements OnClickListener {


// ……..
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

43
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

LinearLayout layout = new LinearLayout(this);


// ……..
setContentView(layout);
}

意味着,在执行 onCreate()函数时,会发出一个讯息给 LinearLayout 类别,


要求其诞生一个新对象。如果想表达这项讯息传递,可以使用顺序图的<Create
Message>图素。其步骤为:

Step-1. 先拉出一个 LinearLayout 生命线,如下:

Step-2. 选取<Message>图素,如下:

先点选了这个图素,接着将鼠标移动到 myActivity 生命线的 onCreate()


活 动 (即 onCreate()讯 息 箭 头 所 指 向 的 长 方 形 框 框 )图 示 上 , 按 住 并 拖 拉 到
LinearLayout 生命线才放开,就出现:

44
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

就能输入「诞生对象」的讯息名称,例如:new(),如下:

如果想表达出 LinearLayout 对象诞生之后,才发出 setContentView()讯息;

45
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

此时可以使用<Reply Message>图素,如下:

先点选了这个图素,接着将鼠标移动到 LinearLayout 的 new()活动(长方形框框)


里,出现如下:

按键之后,出现如下:

46
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这就表明了,在诞生 LinearLayout 对象之后,才发出 setContentView()讯


息。这就是顺序图的特色:明确叙述对象互动过程中,讯息传递的先后顺序。
同样地,可以在拉出一个图素来要求诞生 Button 对象,如下:

47
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

上图就表达了这段程序码里的:

public class myActivity extends Activity implements OnClickListener {


private Button btn;
// ……..
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
// …….
PlayBtn = new Button(this);
// ……..
setContentView(layout);
}

这就表明了,诞生了 LinearLayout 对象之后,接着诞生 Button 对象;然


后才发出 setContentView()讯息。
以上,使用一张顺序图表达 Android 程序在创建屏幕画面的布局(Layout)
阶段的对象互动和讯息传递顺序。

3.3 范例:使用 MediaPlayer 播放音乐


以上,使用一张顺序图表达 Android 程序在创建屏幕画面的布局(Layout)
阶段的对象互动和讯息传递顺序。接着,下一阶段是,用户在屏幕画面上,
触摸画面上的按钮(Button)等动作和引发的事件。为了表达这新阶段的事件追
踪,可以在开启一张新的顺序图。例如,可先把上述顺序图存盘,称为一个
模型文件(一个模型文件可以含有各种图表)。于是,选取主画面上的<Save As>
选项,如下:

并取名为:Sequence DiagramA,就存档如下:

48
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

点 选 <Sequence DiagramA> , 按 右 键 , 并 选 取 <Create Diagram><Add


Sequence Diagram>,如下:

就产生一张新的顺序图了:

49
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

兹拉出 4 条生命线,如下:

再从 WMS 拉一条讯息箭头 Button,出现:

50
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在顺序图里填入讯息名称,并在左边属性表(Attribute Table)里填入条件
(Guard)名称,如下:

就得到顺序图:

51
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这意味着,在 Android 环境里,当 WMS(WindowManagerService)发现了,


用户触摸(Click)屏幕上按钮,而且所按下的是<PlayBtn>按钮;就传递 onClick()
讯息给 Button。此时,Button 又将此 onClick 讯息传送给 myActivity,触发
myActivity 去执行 onClick()函数。如下图:

52
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在执行 onClick()函数时,就发出 Create()讯息给 MediaPlayer,要求其诞


生对象,开启播放器。播放器准备就绪,就发出 start()讯息要求开始播放音乐
或影片。上图表达了你所熟悉的 Android 程序码:

public class myActivity extends Activity implements OnClickListener {


private Button PlayBtn, StopBtn;
private MediaPlayer mPlayer = null;
// ………
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
// ……..
PlayBtn = new Button(this); PlayBtn.setId(101);
StopBtn = new Button(this); PlayBtn.setId(102);
// ……..
layout.addView(PlayBtn, …);
layout.addView(StopBtn, …);
setContentView(layout);
}
public void onClick(View v) {
switch (v.getId()) {
case 101:
mPlayer = MediaPlayer.create(this, R.raw.test_cbr);
mPlayer.start();
break;
case 102:
mPlayer.stop();
// …….
}
}

如果,WMS 发现用户触摸(Click)屏幕上按钮,而且所按下的是<StopBtn>
按钮;就传递 onClick()讯息给 Button。此时,Button 又将此 onClick 讯息传送
给 myActivity,触发 myActivity 去执行 onClick()函数。如下图:

53
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

上图表达了你所熟悉的 Android 程序码:


public class myActivity extends Activity implements OnClickListener {
private Button PlayBtn, StopBtn;
private MediaPlayer mPlayer = null;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
PlayBtn = new Button(this); PlayBtn.setId(101);
StopBtn = new Button(this); PlayBtn.setId(102);
// ……..
layout.addView(PlayBtn, …); layout.addView(StopBtn, …);
setContentView(layout);
}
public void onClick(View v) {
switch (v.getId()) {
case 101: mPlayer = MediaPlayer.create(this, R.raw.test_cbr);
mPlayer.start(); break;
case 102: mPlayer.stop();
// …….
}}

从顺序图更能清晰看出用户、框架、App 和 MediaPlayer 之间的互动,以


及事件的传递顺序。◆

54
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

高煥堂 老師 即將(6 月下旬)在 北京、深圳各講授


<Android 架構師、軟硬整合與應用框架開發> 公開課:

慶祝新開班,搶鮮特惠價,請勿錯過良機
詳細說明,請看<Android 論壇技術教育中心>網頁:
http://www.android1.net/?Forum32/thread-22965-1-1
或詢問:
AndroidEdu520@gmail.com (劉智勇收)

55
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

第 4 章
30 分钟认识 UML 用例图

4.1 绘制 UML 用例图(Use Case Diagram)


启动了 Astah,在主画面上点选< Diagram>,出现:

56
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接着,点选<UseCase Diagram>,就会出现一张空白的类别图,如下:

在空白的类别图上方,有一排类别图的元素(Element),简称「图素」,如下:

此列元素中的左边第 3 个就是「用例」(Use Case)图素。当你点选此图素


(如上图所示),然后移动鼠标(Cursor)到图表里的特定位置,并按下鼠标左键,
就在图表里出现一个用例图素,如下:

57
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这让你输入用例的名称,例如取名为:Play MP4 用例,如下:

一个用例(Use Case)就是用户(User)来使用系统时,该用户期望系统提供
的一项功能或服务。所以每一个用例都会联结到它的用户,在 UML 里称之为
启动者(Actor)。这 Actor 的图素如下:

先点选了这个<Actor>图素,接着将鼠标移动到用例图里的任何位置,并
替它取个名称(例如“User”)按键,就出现如下:

58
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

每一个用例都会连结到它的用户。为了表达这项连结关系,可选取
<Association>图素如下:

先点选这个图素,然后将鼠标移动到 User 图素,按住并拖拉到 Play MP4


用例,就出现了:

刚才提到过,用户(User)对一个系统服务的期待(Expectation)就是一个用
例。一般而言,用户可能会对系统怀有多项的期待,此时该用户会连结到多
个用例。比方说,一个用户使用播放器的两项服务:Play MP4 和 Stop MP4。
就能在用例图上表达如下:

59
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在这个 Play MP4 用例里,内含有一段「下载(Loading)」MP4 的小服务,


而且 User 能感觉得到它带来的感受。此时,可以将之独立出来成为一个用例。
如下:

那么,如何表达 Play MP4 用例「内含」有一段 Loading 小用例呢? 此时,可


选取<Include>图素如下:

60
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

先点选这个图素,然后将鼠标移动到 Play MP4 用例,按住并拖拉到


Loading 小用例,就出现:

这意味着,Loading 小服务(即用例)是 Play MP4 大服务里的一个片段,而


且是必要的片段。换句话说,在执行 Play MP4 服务的过程中,必然会去执行
Loading 小服务。
同理,也可以再将 Play MP4 用例里的其它片段(如 Playing)独立出来,成
为另一个小用例。如下:

61
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这意味着,用户怀着「播放 MP4」的目的(或期待)来使用播放系统,此时
系统必须提供服务来满足用户的这项目的。而所提供的服务里,包含了 Loading
和 Playing 两项小服务。再看下图:

62
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这图说明了,有些情形下,小用例事先已经存在了,后来才出现大用例。
此时就能直接从新的大服务去包含(Include)已存在的小用例,可以减少对小用
例的重复描述(Description)。例如,用户提出一项新用例:Load MP3,其从云
端下载 MP3 的过程与下载 MP4 是一样的,于是就让新的 Load MP3 用例去包
含(Include)已存在的 Loading 小用例(如上图)。这意味着,Loading 小用例是
Load MP3 大用例里的一个片段,而且是必要的片段。换句话说,在执行 Load
MP3 服务的过程中,必然会去执行 Loading 小服务。
上述的<<include>>图素表达了两个用例之间「必要性」包含关系。此外,
UML 还 提 供 了 「 可 选 择 性 」 的 包 含 关 系 , 又 称 为 扩 充 (Extend) 关 系 , 以
<<Extend>>图素表示之,如下:

使用这个图素,可表达两个用例之间的扩充关系,如下图:

63
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这意味着,Save to PlayList 小用例是 Stop MP4 大用例里的一个片段;然而


它并非必要的片段。换句话说,在执行 Stop MP4 服务的过程中,会视用户的
选择或系统的状态而决定是否去执行 Save to PlayList 小服务。
就如同去汉堡店买套餐时,正常情形下,不会执行「赠送玩具」小用例,
然而有些小孩会要求赠送玩具。如下:

買漢堡特餐
Normal Scenario
Alternative Scenario

<<extend>>

贈送玩具

4.2 细说<<Include>>与<<Extend>>图素
通常,大用例表达出正常的情境(Normal Scenario),而新附加的小表达出
特殊或例外的程序(Alternative Scenario)。有些情形下,大用例事先已经存在了,
后来才出现小用例来做补充。此时就很轻松地让小用例去补充(Extend)已存在
的 大 用 例 , 可 以 减 少 对 大 用 例 的 重 复 描 述 。 为 了 让 你 对 <<Include>> 和
<<Extend>>两个图素有更精致的体会,兹再举一个例子来说明之:

64
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

此图说明了,当购买有一部多功能咖啡机时,用户有三种目的:煮热开
水、主温开水和煮咖啡;就以 UML 的用例图表示如上图所示。
也是因为这些功能,顾客才会买这咖啡机。其中,煮热开水和主温开水
两项用例,共同拥有一项服务片段(如 Boiling Water),就独立出来如下图:

接着又发现,在使用煮咖啡功能时,有些用户不想加糖、有些则不想奶
精;于是,使用<<Extend>>图素表达如下:

65
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

4.3 范例:用例图 + 类别图 + 顺序图


4.3.1 建模范例

本范例是常见的 Android 应用程序架构,看看如何使用你已经学过的类别


图、顺序图和用例图来表示之。一方面展示这三种图的关联性,另一方面让
你能联合使用三种图来建立一个有用的模型。首先启动了 Astah,建立新模型,
出现:

66
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

先点选 no_title,并选取<Create Model><Add Package>来诞生一个新的套件,


如下:

接着,点选 package0,并选取<Create Diagram><Add UseCase Diagram>来诞生


一个新的用例图,如下:

67
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

就建立了一个新的空白用例图,如下:

兹建立一个简单的用例图,如下:

68
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

从这个用例里,独立出一个小用例:

再独立出另一个小用例:

69
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

用例就是表达用户使用 App 的互动(或对话)过程。在 Android 环境里,需


要规划画面布局(Layout)来担任互动的接口( UI)。于此,布局的规划方法如下:

由 ac01_layout 负责两个用例的互动接口;而 pu_layout 则负责另一个用例


的互动接口。如下图:

70
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

(ac01_layout 布局 ) (pu_layout 布局 )

接着,规画 Activity 得子类别来支持上述的画面布局。最简单规则是:


z 一个 Layout Å(对应)Æ 一个 Activity 类别。

也就是,定义两个 Activity 的子类别,各支持一个画面布局:


z 由 ac01 类别来支持 ac01_layout 布局。
z 由 pickup 类别来支持 pu_layout 布局。

现在,就来将这两个类别,表达于类别图里。先点选 package0,并选取<Create
Diagram><Add Class Diagram>来诞生一个新的空白类别图,如下:

在这新的类别图里,呈现出 Android 框架里的 Activity 类别,以及 App


里的 ac01 和 pickup 两个类别:

71
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

并且以< Generation>图素来表达出:ac01 和 pickup 两者都是 Activity 的


子类别。接下来,并选取<Create Diagram><Add Sequence Diagram>建立一个
新的空白顺序图:

目前在 package0 里,含有 3 章图表,以及 6 个已经建立的图素(如下图)。

72
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

现在,点选 User 图素如下:

然后,将它拖拉到右边的顺序图里,得到:

同样地,继续点选 ac01 图素:

73
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

然后,将它拖拉到右边的顺序图里,得到:

由于一个类别可以诞生多个对象,这个 ac01 类别也能有多个对象,各个


对象都可以有自己的名称。此时,可以点选 ac01 生命线,并在左边属性表
(Attribute Table)里填入对象名称。在 Android 程序里,一个类别可以支持多的
布局(Layout),而布局也是对象,所以也能将布局名称视为对象名称而填入上
述的属性表里,如下:

74
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

填入布局名称之后,得到:

同样地,再拉出 pickup 图素,并填入布局名称如下:

75
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

最后,从图素表里点选<Lifeline>图素,如下:

就 可 以 建 立 一 个 新 的 生 命 线 , 并 取 名 为 : SP, 其 表 示 Android 里 的
SharedPreferences 资料暂存机制。如下图:

76
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

现在,来表达这些对象的互动,及其讯息传递顺序。
z 用户启动 App 程序时,透过 Android 框架传送 Create()讯息给 ac01,触发
它去执行 onCreate()函数来建立 ac01_layout 布局。

z 用户从 ac01_layout 布局上的 Menu 菜单选取<Pick Up An Item>选项如下:

z 此刻,ac01 透过 Android 框架发送 startActivity()讯息给 pickup:

77
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

触发它显示出 pu_layout 画面布局。如下:

z 用户从 pu_layout 布局上的 Menu 选单里挑选一个选项。Pickup 就发送


saveItem()讯息给 SP,把选项内容存入到 SharedPreferences 机制里,然后
结束了 pu_layout,返回到 ac01_layout。

78
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

z 用户从 ac01_layout 布局上的 Menu 选取<Show Result>选项。ac01 传送讯


息给 SharedPreferences,从它取得刚才寄存的选项内容,并显示于画面上。

79
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

以上完成了范例的建模过程。

4.3.2 落实为 Android 应用程序码

上述模型,可以轻易落实为 Android 的应用程序码,如下:


// ac01.java
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class ac01 extends Activity {


public static final int PICKUP_ID = Menu.FIRST;
public static final int SHOW_ID = Menu.FIRST + 1;

@Override public void onCreate(Bundle icicle) {


super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, PICKUP_ID, 0, "Pick Up An Item");
menu.add(0, SHOW_ID, 1, "Show Result");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case PICKUP_ID:
Intent in = new Intent(ac01.this, pickup.class);
startActivity(in); return true;
case SHOW_ID:
SharedPreferences passwdfile = getSharedPreferences( "ITEM", 0);
String im = passwdfile.getString("ITEM", null);
TextView tv = (TextView)findViewById(R.id.tv);
tv.setText("choice: " + im);
return true;
}
return super.onOptionsItemSelected(item);
}}

// pickup.java
import android.app.Activity;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

80
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

public class pickup extends Activity {


public static final int ITEM_1_ID = Menu.FIRST;
public static final int ITEM_2_ID = Menu.FIRST + 1;
public static final int ITEM_3_ID = Menu.FIRST + 2;

@Override public void onCreate(Bundle icicle) {


super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, ITEM_1_ID, 0, "item-1");
menu.add(0, ITEM_2_ID, 1, "item-2");
menu.add(0, ITEM_3_ID, 2, "item-3");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Editor passwdfile = getSharedPreferences("ITEM", 0).edit();
passwdfile.putString("ITEM",item.getTitle().toString());
passwdfile.commit();
finish();
return super.onOptionsItemSelected(item);
}}

其中,ac01 类别里的指令:
Intent in = new Intent(ac01.this, pickup.class);
startActivity(in);
就启动了 pickup。在 pickup 类别里的指令:
Editor passwdfile = getSharedPreferences("ITEM", 0).edit();
passwdfile.putString("ITEM",item.getTitle().toString());
passwdfile.commit();
finish();

将 item 值存入 SharedPreferences 里。返回到 ac01_layout 的画面后,指令:


SharedPreferences passwdfile = getSharedPreferences("ITEM", 0);
String im = passwdfile.getString("ITEM", null);
TextView tv = (TextView)findViewById(R.id.tv);
tv.setText("choice: " + im);

从 SharedPreferences 取出 item 值,显示于布局画面上。◆

~~ Continued ~~

81

You might also like