轉自:http://www.cppblog.com/API/archive/2013/07/04/201506.htmlhtml
在UML 2.0的13種圖形中,類圖是使用頻率最高的UML圖之一。Martin Fowler在其著做《UML Distilled: A Brief Guide to the Standard Object Modeling Language, Third Edition》(《UML精粹:標準對象建模語言簡明指南(第3版)》)中有這麼一段:「If someone were to come up to you in a dark alley and say, 'Psst, wanna see a UML diagram?' that diagram would probably be a class diagram. The majority of UML diagrams I see are class diagrams.」(「若是有人在黑暗的小巷中向你走來並對你說:‘嘿,想不想看一張UML圖?’那麼這張圖頗有可能就是一張類圖,我所見過的大部分的UML圖都是類圖」),因而可知類圖的重要性。數據庫
類圖用於描述系統中所包含的類以及它們之間的相互關係,幫助人們簡化對系統的理解,它是系統分析和設計階段的重要產物,也是系統編碼和測試的重要模型依據。編程
1. 類編程語言
類(Class)封裝了數據和行爲,是面向對象的重要組成部分,它是具備相同屬性、操做、關係的對象集合的總稱。在系統中,每一個類都具備必定的職責,職責指的是類要完成什麼樣的功能,要承擔什麼樣的義務。一個類能夠有多種職責,設計得好的類通常只有一種職責。在定義類的時候,將類的職責分解成爲類的屬性和操做(即方法)。類的屬性即類的數據職責,類的操做即類的行爲職責。設計類是面向對象設計中最重要的組成部分,也是最複雜和最耗時的部分。ide
在軟件系統運行時,類將被實例化成對象(Object),對象對應於某個具體的事物,是類的實例(Instance)。函數
類圖(Class Diagram)使用出如今系統中的不一樣類來描述系統的靜態結構,它用來描述不一樣的類以及它們之間的關係。工具
在系統分析與設計階段,類一般能夠分爲三種,分別是實體類(Entity Class)、控制類(Control Class)和邊界類(Boundary Class),下面對這三種類加以簡要說明:學習
(1) 實體類:實體類對應系統需求中的每一個實體,它們一般須要保存在永久存儲體中,通常使用數據庫表或文件來記錄,實體類既包括存儲和傳遞數據的類,還包括操做數據的類。實體類來源於需求說明中的名詞,如學生、商品等。測試
(2) 控制類:控制類用於體現應用程序的執行邏輯,提供相應的業務操做,將控制類抽象出來能夠下降界面和數據庫之間的耦合度。控制類通常是由動賓結構的短語(動詞+名詞)轉化來的名詞,如增長商品對應有一個商品增長類,註冊對應有一個用戶註冊類等ui
(3) 邊界類:邊界類用於對外部用戶與系統之間的交互對象進行抽象,主要包括界面類,如對話框、窗口、菜單等。
在面向對象分析和設計的初級階段,一般首先識別出實體類,繪製初始類圖,此時的類圖也可稱爲領域模型,包括實體類及其它們之間的相互關係。
2. 類的UML圖示
在UML中,類使用包含類名、屬性和操做且帶有分隔線的長方形來表示,如定義一個Employee類,它包含屬性name、age和email,以及操做modifyInfo(),在UML類圖中該類如圖1所示:
圖1 類的UML圖示
圖1對應的Java代碼片斷以下:
public class Employee { private String name; private int age; private String email; public void modifyInfo() { ...... } } |
在UML類圖中,類通常由三部分組成:
(1) 第一部分是類名:每一個類都必須有一個名字,類名是一個字符串。
(2) 第二部分是類的屬性(Attributes):屬性是指類的性質,即類的成員變量。一個類能夠有任意多個屬性,也能夠沒有屬性
UML規定屬性的表示方式爲:
可見性 名稱:類型 [ = 缺省值 ]
其中:
(3) 第三部分是類的操做(Operations):操做是類的任意一個實例對象均可以使用的行爲,是類的成員方法。
UML規定操做的表示方式爲:
可見性 名稱(參數列表) [ : 返回類型]
其中:
在類圖2中,操做method1的可見性爲public(+),帶入了一個Object類型的參數par,返回值爲空(void);操做method2的可見性爲protected(#),無參數,返回值爲String類型;操做method3的可見性爲private(-),包含兩個參數,其中一個參數爲int類型,另外一個爲int[]類型,返回值爲int類型。
圖2 類圖操做說明示意圖
因爲在Java語言中容許出現內部類,所以可能會出現包含四個部分的類圖,如圖3所示:
圖3 包含內部類的類圖
類與類之間的關係(1)
在軟件系統中,類並非孤立存在的,類與類之間存在各類關係,對於不一樣類型的關係,UML提供了不一樣的表示方式。
1. 關聯關係
關聯(Association)關係是類與類之間最經常使用的一種關係,它是一種結構化關係,用於表示一類對象與另外一類對象之間有聯繫,如汽車和輪胎、師傅和徒弟、班級和學生等等。在UML類圖中,用實線鏈接有關聯關係的對象所對應的類,在使用Java、C#和C++等編程語言實現關聯關係時,一般將一個類的對象做爲另外一個類的成員變量。在使用類圖表示關聯關係時能夠在關聯線上標註角色名,通常使用一個表示二者之間關係的動詞或者名詞表示角色名(有時該名詞爲實例對象名),關係的兩端表明兩種不一樣的角色,所以在一個關聯關係中能夠包含兩個角色名,角色名不是必須的,能夠根據須要增長,其目的是使類之間的關係更加明確。
如在一個登陸界面類LoginForm中包含一個JButton類型的註冊按鈕loginButton,它們之間能夠表示爲關聯關係,代碼實現時能夠在LoginForm中定義一個名爲loginButton的屬性對象,其類型爲JButton。如圖1所示:
圖1 關聯關係實例
圖1對應的Java代碼片斷以下:
public class LoginForm { private JButton loginButton; //定義爲成員變量 …… } public class JButton { …… } |
在UML中,關聯關係一般又包含以下幾種形式:
(1) 雙向關聯
默認狀況下,關聯是雙向的。例如:顧客(Customer)購買商品(Product)並擁有商品,反之,賣出的商品總有某個顧客與之相關聯。所以,Customer類和Product類之間具備雙向關聯關係,如圖2所示:
圖2 雙向關聯實例
圖2對應的Java代碼片斷以下:
public class Customer { private Product[] products; …… } public class Product { private Customer customer; …… } |
(2) 單向關聯
類的關聯關係也能夠是單向的,單向關聯用帶箭頭的實線表示。例如:顧客(Customer)擁有地址(Address),則Customer類與Address類具備單向關聯關係,如圖3所示:
圖3 單向關聯實例
圖3對應的Java代碼片斷以下:
public class Customer { private Address address; …… } public class Address { …… } |
(3) 自關聯
在系統中可能會存在一些類的屬性對象類型爲該類自己,這種特殊的關聯關係稱爲自關聯。例如:一個節點類(Node)的成員又是節點Node類型的對象,如圖4所示:
圖4 自關聯實例
圖4對應的Java代碼片斷以下:
public class Node { private Node subNode; …… } |
(4) 多重性關聯
多重性關聯關係又稱爲重數性(Multiplicity)關聯關係,表示兩個關聯對象在數量上的對應關係。在UML中,對象之間的多重性能夠直接在關聯直線上用一個數字或一個數字範圍表示。
對象之間能夠存在多種多重性關聯關係,常見的多重性表示方式如表1所示:
表1 多重性表示方式列表
表示方式
|
多重性說明
|
1..1
|
表示另外一個類的一個對象只與該類的一個對象有關係 |
0..*
|
表示另外一個類的一個對象與該類的零個或多個對象有關係 |
1..*
|
表示另外一個類的一個對象與該類的一個或多個對象有關係 |
0..1
|
表示另外一個類的一個對象沒有或只與該類的一個對象有關係 |
m..n
|
表示另外一個類的一個對象與該類最少m,最多n個對象有關係 (m≤n) |
例如:一個界面(Form)能夠擁有零個或多個按鈕(Button),可是一個按鈕只能屬於一個界面,所以,一個Form類的對象能夠與零個或多個Button類的對象相關聯,但一個Button類的對象只能與一個Form類的對象關聯,如圖5所示:
圖5 多重性關聯實例
圖5對應的Java代碼片斷以下:
public class Form { private Button[] buttons; //定義一個集合對象 …… } public class Button { …… } |
(5) 聚合關係
聚合(Aggregation)關係表示總體與部分的關係。在聚合關係中,成員對象是總體對象的一部分,可是成員對象能夠脫離總體對象獨立存在。在UML中,聚合關係用帶空心菱形的直線表示。例如:汽車發動機(Engine)是汽車(Car)的組成部分,可是汽車發動機能夠獨立存在,所以,汽車和發動機是聚合關係,如圖6所示:
圖6 聚合關係實例
在代碼實現聚合關係時,成員對象一般做爲構造方法、Setter方法或業務方法的參數注入到總體對象中,圖6對應的Java代碼片斷以下:
public class Car { private Engine engine; //構造注入 public Car(Engine engine) { this.engine = engine; } //設值注入 public void setEngine(Engine engine) { this.engine = engine; } …… } public class Engine { …… } |
(6) 組合關係
組合(Composition)關係也表示類之間總體和部分的關係,可是在組合關係中總體對象能夠控制成員對象的生命週期,一旦總體對象不存在,成員對象也將不存在,成員對象與總體對象之間具備同生共死的關係。在UML中,組合關係用帶實心菱形的直線表示。例如:人的頭(Head)與嘴巴(Mouth),嘴巴是頭的組成部分之一,並且若是頭沒了,嘴巴也就沒了,所以頭和嘴巴是組合關係,如圖7所示:
圖7 組合關係實例
在代碼實現組合關係時,一般在總體類的構造方法中直接實例化成員類,圖7對應的Java代碼片斷以下:
public class Head { private Mouth mouth; public Head() { mouth = new Mouth(); //實例化成員類 } …… } public class Mouth { …… } |
類與類之間的關係(2)
2. 依賴關係
依賴(Dependency)關係是一種使用關係,特定事物的改變有可能會影響到使用該事物的其餘事物,在須要表示一個事物使用另外一個事物時使用依賴關係。大多數狀況下,依賴關係體如今某個類的方法使用另外一個類的對象做爲參數。在UML中,依賴關係用帶箭頭的虛線表示,由依賴的一方指向被依賴的一方。例如:駕駛員開車,在Driver類的drive()方法中將Car類型的對象car做爲一個參數傳遞,以便在drive()方法中可以調用car的move()方法,且駕駛員的drive()方法依賴車的move()方法,所以類Driver依賴類Car,如圖1所示:
圖1 依賴關係實例
在系統實施階段,依賴關係一般經過三種方式來實現,第一種也是最經常使用的一種方式是如圖1所示的將一個類的對象做爲另外一個類中方法的參數,第二種方式是在一個類的方法中將另外一個類的對象做爲其局部變量,第三種方式是在一個類的方法中調用另外一個類的靜態方法。圖1對應的Java代碼片斷以下:
public class Driver { public void drive(Car car) { car.move(); } …… } public class Car { public void move() { ...... } …… } |
3. 泛化關係
泛化(Generalization)關係也就是繼承關係,用於描述父類與子類之間的關係,父類又稱做基類或超類,子類又稱做派生類。在UML中,泛化關係用帶空心三角形的直線來表示。在代碼實現時,咱們使用面向對象的繼承機制來實現泛化關係,如在Java語言中使用extends關鍵字、在C++/C#中使用冒號「:」來實現。例如:Student類和Teacher類都是Person類的子類,Student類和Teacher類繼承了Person類的屬性和方法,Person類的屬性包含姓名(name)和年齡(age),每個Student和Teacher也都具備這兩個屬性,另外Student類增長了屬性學號(studentNo),Teacher類增長了屬性教師編號(teacherNo),Person類的方法包括行走move()和說話say(),Student類和Teacher類繼承了這兩個方法,並且Student類還新增方法study(),Teacher類還新增方法teach()。如圖2所示:
圖2 泛化關係實例
圖2對應的Java代碼片斷以下:
//父類 public class Person { protected String name; protected int age; public void move() { …… } public void say() { …… } } //子類 public class Student extends Person { private String studentNo; public void study() { …… } } //子類 public class Teacher extends Person { private String teacherNo; public void teach() { …… } } |
4. 接口與實現關係
在不少面嚮對象語言中都引入了接口的概念,如Java、C#等,在接口中,一般沒有屬性,並且全部的操做都是抽象的,只有操做的聲明,沒有操做的實現。UML中用與類的表示法相似的方式表示接口,如圖3所示:
圖3 接口的UML圖示
接口之間也能夠有與類之間關係相似的繼承關係和依賴關係,可是接口和類之間還存在一種實現(Realization)關係,在這種關係中,類實現了接口,類中的操做實現了接口中所聲明的操做。在UML中,類與接口之間的實現關係用帶空心三角形的虛線來表示。例如:定義了一個交通工具接口Vehicle,包含一個抽象操做move(),在類Ship和類Car中都實現了該move()操做,不過具體的實現細節將會不同,如圖4所示:
圖4 實現關係實例
實現關係在編程實現時,不一樣的面嚮對象語言也提供了不一樣的語法,如在Java語言中使用implements關鍵字,而在C++/C#中使用冒號「:」來實現。圖4對應的Java代碼片斷以下:
public interface Vehicle { public void move(); } public class Ship implements Vehicle { public void move() { …… } } public class Car implements Vehicle { public void move() { …… } } |
實例分析1——登陸模塊
某基於C/S的即時聊天系統登陸模塊功能描述以下:
用戶經過登陸界面(LoginForm)輸入帳號和密碼,系統將輸入的帳號和密碼與存儲在數據庫(User)表中的用戶信息進行比較,驗證用戶輸入是否正確,若是輸入正確則進入主界面(MainForm),不然提示「輸入錯誤」。
根據以上描述繪製初始類圖。
參考解決方案:
參考類圖以下:
考慮到系統擴展性,在本實例中引入了抽象數據訪問接口IUserDAO,再將具體數據訪問對象注入到業務邏輯對象中,可經過配置文件(如XML文件)等方式來實現,將具體的數據訪問類類名存儲在配置文件中,若是須要更換新的具體數據訪問對象,只需修改配置文件便可,原有程序代碼無須作任何修改。
類說明:
類 名
|
說 明
|
LoginForm | 登陸窗口,省略界面組件和按鈕事件處理方法(邊界類) |
LoginBO | 登陸業務邏輯類,封裝實現登陸功能的業務邏輯(控制類) |
IUserDAO | 抽象數據訪問類接口,聲明對User表的數據操做方法,省略除查詢外的其餘方法(實體類) |
UserDAO | 具體數據訪問類,實現對User表的數據操做方法,省略除查詢外的其餘方法(實體類) |
MainForm | 主窗口(邊界類) |
方法說明:
方法名
|
說 明
|
LoginForm類的LoginForm()方法 | LoginForm構造函數,初始化實例成員 |
LoginForm類的validate()方法 | 界面類的驗證方法,經過調用業務邏輯類LoginBO的validate()方法實現對用戶輸入信息的驗證 |
LoginBO類的validate()方法 | 業務邏輯類的驗證方法,經過調用數據訪問類的findUserByAccAndPwd()方法驗證用戶輸入信息的合法性 |
LoginBO類的setIUserDAO()方法 | Setter方法,在業務邏輯對象中注入數據訪問對象(注意:此處針對抽象數據訪問類編程 |
IUserDAO接口的findUserByAccAndPwd()方法 | 業務方法聲明,經過用戶帳號和密碼在數據庫中查詢用戶信息,判斷該用戶身份的合法性 |
UserDAO類的findUserByAccAndPwd()方法 | 業務方法實現,實如今IUserDAO接口中聲明的數據訪問方法 |
實例分析2——註冊模塊
某基於Java語言的C/S軟件須要提供註冊功能,該功能簡要描述以下:
用戶經過註冊界面(RegisterForm)輸入我的信息,用戶點擊「註冊」按鈕後將輸入的信息經過一個封裝用戶輸入數據的對象(UserDTO)傳遞給操做數據庫的數據訪問類,爲了提升系統的擴展性,針對不一樣的數據庫可能須要提供不一樣的數據訪問類,所以提供了數據訪問類接口,如IUserDAO,每個具體數據訪問類都是某一個數據訪問類接口的實現類,如OracleUserDAO就是一個專門用於訪問Oracle數據庫的數據訪問類。
根據以上描述繪製類圖。爲了簡化類圖,我的信息僅包括帳號(userAccount)和密碼(userPassword),且界面類無需涉及界面細節元素。
參考解決方案:
在以上功能說明中,能夠分析出該系統包括三個類和一個接口,這三個類分別是註冊界面類RegisterForm、用戶數據傳輸類UserDTO、Oracle用戶數據訪問類OracleUserDAO,接口是抽象用戶數據訪問接口IUserDAO。它們之間的關係以下:
(1) 在RegisterForm中須要使用UserDTO類傳輸數據且須要使用數據訪問類來操做數據庫,所以RegisterForm與UserDTO和IUserDAO之間存在關聯關係,在RegisterForm中能夠直接實例化UserDTO,所以它們之間可使用組合關聯。
(2) 因爲數據庫類型須要靈活更換,所以在RegisterForm中不能直接實例化IUserDAO的子類,能夠針對接口IUserDAO編程,再經過注入的方式傳入一個IUserDAO接口的子類對象(在本書後續章節中將學習如何具體實現),所以RegisterForm和IUserDAO之間具備聚合關聯關係。
(3) OracleUserDAO是實現了IUserDAO接口的子類,所以它們之間具備類與接口的實現關係。
(4) 在聲明IUserDAO接口的增長用戶信息方法addUser()時,須要將在界面類中實例化的UserDTO對象做爲參數傳遞進來,而後取出封裝在UserDTO對象中的數據插入數據庫,所以addUser()方法的函數原型能夠定義爲:public boolean addUser(UserDTO user),在IUserDAO的方法addUser()中將UserDTO類型的對象做爲參數,故IUserDAO與UserDTO存在依賴關係。
經過以上分析,該實例參考類圖如圖1所示:
圖1 註冊功能參考類圖
注意:在繪製類圖或其餘UML圖形時,能夠經過註釋(Comment)來對圖中的符號或元素進行一些附加說明,若是須要詳細說明類圖中的某一方法的功能或者實現過程,可使用如圖2所示表示方式:
圖2 類圖註釋實例
實例分析3——售票機控制程序
某運輸公司決定爲新的售票機開發車票銷售的控制軟件。圖I給出了售票機的面板示意圖以及相關的控制部件。
圖I 售票機面板示意圖
售票機相關部件的做用以下所述:
(1) 目的地鍵盤用來輸入行程目的地的代碼(例如,200表示總站)。
(2) 乘客能夠經過車票鍵盤選擇車票種類(單程票、屢次往返票和座席種類)。
(3) 繼續/取消鍵盤上的取消按鈕用於取消購票過程,繼續按鈕容許乘客連續購買多張票。
(4) 顯示屏顯示全部的系統輸出和用戶提示信息。
(5) 插卡口接受MCard(現金卡),硬幣口和紙幣槽接受現金。
(6) 打印機用於輸出車票。
(7) 全部部件都可實現自檢並恢復到初始狀態。
現採用面向對象方法開發該系統,使用UML進行建模,繪製該系統的初始類圖。
參考解決方案:
參考類圖以下:
類說明:
類 名
|
說 明
|
Component | 抽象部件類,全部部件類的父類 |
Keyboard | 抽象鍵盤類 |
ActionKeyboard | 繼續/取消鍵盤類 |
TicketKindKeyboard | 車票種類鍵盤類 |
DestinationKeyboard | 目的地鍵盤類 |
Screen | 顯示屏類 |
CardDriver | 卡驅動器類 |
CashSlot | 現金(硬幣/紙幣)槽類 |
Printer | 打印機類 |
TicketSoldSystem | 售票系統類 |
方法說明:
方法名
|
說 明
|
Component 的init()方法 | 初始化部件 |
Component 的doSeltTest()方法 | 自檢 |
Keyboard的getSelectedKey()方法 | 獲取按鍵值 |
ActionKeyboard的getAction()方法 | 繼續/取消鍵盤事件處理 |
TicketKindKeyboard的getTicketKind()方法 | 車票種類鍵盤事件處理 |
DestinationKeyboard的getDestinationCode()方法 | 目的地鍵盤事件處理 |
Screen的showText()方法 | 顯示信息 |
CardDriver的getCredit()方法 | 獲取金額 |
CardDriver的debitFare()方法 | 更新卡餘額 |
CardDriver的ejectMCard()方法 | 退卡 |
CashSlot的getCredit()方法 | 獲取金額 |
Printer的printTicket()方法 | 打印車票 |
Printer的ejectTicket()方法 | 出票 |
TicketSoldSystem的verifyCredit()方法 | 驗證金額 |
TicketSoldSystem的calculateFare()方法 | 計算費用 |