前言
上一篇文章UML類圖(上):類、繼承、實現,講了UML類圖中類、繼承、實現三種關系及其在UML類圖中的畫法,本文將接着上文的內容,繼續講講對象之間的其他幾種關系,主要就是關聯、聚合、組合、依賴,下面開始文章的內容。
關聯關系
關聯(Assocition)關系是類與類之間最常見的一種關系,它是一種結構化的關系,表示一類對象與另一類對象之間有聯系,如汽車和輪胎、師傅和徒弟、班級和學生等。在UML類圖中,用實線連接有關聯關系的對象所對應的類,在Java中通常將一個類的對象作為另一個類的成員變量。關聯關系分單向關聯、雙向關聯、自關聯,逐一看一下。
1、單向關聯關系
單向關聯指的是關聯只有一個方向,比如顧客(Customer)擁有地址(Address),其Java實現為:
public class Address { }
public class Customer { private Address address; }
UML的畫法為:
2、雙向關聯關系
默認情況下的關聯都是雙向的,比如顧客(Customer)購買商品(Product),反之,賣出去的商品總是與某個顧客與之相關聯,這就是雙向關聯。Java類的寫法為:
public class Product { private Customer customer; }
public class Customer { private Product[] product; }
對應的UML類圖應當是:
3、自關聯關系
自關聯,指的就是對象中的屬性為對象本身,這在鏈表中非常常見,單向鏈表Node中會維護一個它的前驅Node,雙向鏈表Node中會維護一個它的前驅Node和一個它的后繼Node。就以單向鏈表為例,它的Java寫法為:
public class Node { private Node nextNode; }
對應的UML類圖應當是:
聚合關系
聚合(Aggregation)關系表示整體與部分的關系。在聚合關系中,成員對象是整體的一部分,但是成員對象可以脫離整體對象獨立存在。在UML中,聚合關系用帶空心菱形的直線表示,如汽車(Car)與引擎(Engine)、輪胎(Wheel)、車燈(Light),Java表示為:
public class Engine { }
public class Wheel { }
public class Light { }
public class Car { private Engine engine; private Light light; private Wheel wheel; public Car(Engine engine, Light light, Wheel wheel) { super(); this.engine = engine; this.light = light; this.wheel = wheel; } public void drive() { } }
對應的UML類圖為:
代碼實現聚合關系,成員對象通常以構造方法、Setter方法的方式注入到整體對象之中。
組合關系
組合(Composition)關系也表示的是一種整體和部分的關系,但是在組合關系中整體對象可以控制成員對象的生命周期,一旦整體對象不存在,成員對象也不存在,整體對象和成員對象之間具有同生共死的關系。在UML中組合關系用帶實心菱形的直線表示。
比如人的頭(Head)和嘴巴(Mouth)、鼻子(Nose),嘴巴和鼻子是頭的組成部分之一,一旦頭沒了,嘴巴也沒了,因此頭和嘴巴、鼻子是組合關系,Java表示為:
public class Mouth { }
public class Nose { }
public class Head { private Mouth mouth; private Nose nose; public Head() { mouth = new Mouth(); nose = new Nose(); } public void shake() { } }
其UML的表示方法為:
代碼實現組合關系,通常在整體類的構造方法中直接實例化成員類,這是因為組合關系的整體和部分是共生關系,如果通過外部注入,那么即使整體不存在,那么部分還是存在的,這就相當於變成了一種聚合關系了。
依賴關系
依賴(Dependency)關系是一種使用關系,特定事物的改變有可能會影響到使用該事物的其他事物,在需要表示一個事物使用另一個事物時使用依賴關系,大多數情況下依賴關系體現在某個類的方法使用另一個類的對象作為參數。在UML中,依賴關系用帶箭頭的虛線表示,由依賴的一方指向被依賴的一方。
比如,駕駛員(Driver)開車,Driver類的drive()方法將車(Car)的對象作為一個參數傳遞,以便在drive()方法中能夠調用car的move()方法,且駕駛員的drive()方法依賴車的move()方法,因此也可以說Driver依賴Car,Java代碼為:
public interface Car { public void move(); }
public class Driver { public void drive(Car car) { car.move(); } }
其UML的畫法為:
依賴關系通常通過三種方式來實現:
- 將一個類的對象作為另一個類中方法的參數
- 在一個類的方法中將另一個類的對象作為其對象的局部變量
- 在一個類的方法中調用另一個類的靜態方法
關聯關系、聚合關系、組合關系之間的區別
從上文可以看出,關聯關系、聚合關系和組合關系三者之間比較相似,本文的最后就來總結一下這三者之間的區別。
關聯和聚合的區別主要在於語義上:關聯的兩個對象之間一般是平等的,聚合則一般是不平等的。
聚合和組合的區別則在語義和實現上都有差別:組合的兩個對象之間生命周期有很大的關聯,被組合的對象在組合對象創建的同時或者創建之后創建,在組合對象銷毀之前銷毀,一般來說被組合對象不能脫離組合對象獨立存在,而且也只能屬於一個組合對象;聚合則不一樣,被聚合的對象可以屬於多個聚合對象。
再舉例子來說:
- 你和你的朋友屬於關聯關系,因為你和你的朋友之間的關系是平等的,關聯關系只是表示一下兩個對象之間的一種簡單的聯系而已,就像我有一個朋友
- 你和你借的書屬於聚合關系,第一是因為書可以獨立存在,第二是因為書不僅僅屬於你,也可以屬於別人,只是暫時你擁有
- 你和你的心臟屬於組合關系,因為你的心臟只是屬於你的,不能脫離與你而存在
不過,實際應用中,我個人感覺三種關系其實沒有區分得這么清楚,有些架構師甚至會說"組合和聚合沒什么區別",所以,有時候不需要把細節扣得這么細,合理利用對象之間的關系給出設計方案即可。