類間關係及UML表示.md
類間關係及UML表示
類間關係是理解面向物件設計原則基礎和設計模式的基礎,因此筆者整理了一番類間關係及其UML表示相關的知識,也作為個人學習的總結。
1.依賴關係
對於兩個相對獨立的物件,當一個物件負責構造另一個物件的例項,或者依賴另一個物件的服務時,這兩個物件之間主要體現為依賴關係。
可以簡單的理解為,一個類A使用到了另一個類B,而這種使用關係是具有偶然性的、臨時性的、非常弱的,但是B類的變化會影響到A。
例如:充電電池通過充電器來充電,自行車通過打氣筒來充氣,人藉助螺絲刀擰螺絲,人借用船過河,藝術家鑑賞藝術品,警察抓小偷,學生讀書,某人買車。
表現在程式碼層面,為類B作為引數被類A在某個方法中使用
class B{}
class A{
//作為方法中的引數
f1(B b)
//作為返回值
B f2()
//區域性變數
f3(){B b = new B();}
}
例如:
class Car
{
// Do something
};
class Person
{
void move(Car car);
};
說明:人的快速移動需要有車的協助, 但是這種依賴是比較弱的, 就是人也可以不用車而用其他工具, 注意:與關聯不同的是人不必擁有這輛車,而只要使用就行。
UML表示方法:虛線箭頭,類A指向類B。
依賴關係的分類
依賴關係可分為單向依賴、自身依賴、雙向依賴。以下舉實際例子分別說明。
(1)單向依賴
一隻老鼠在蘋果園中吃蘋果,老鼠每吃一個蘋果,就根據該蘋果的所含能量增加一些體重。結合上邊給出的模型,設計老鼠和蘋果類。
//蘋果類 class Apple { private int power; Apple(int e){ this.power = e; } int getEnergy(){ return power; } } //老鼠類 class Mouse { private int weight; Mouse(int weight){ this.weight = weight; } public int getWeight(){ return weight; } void eat(Apple apple){ weight+=apple.getEnergy(); } }
拓展:
或:
(2)自身依賴
例子:
一個遊戲中有很多怪物(Monster),怪物之間可能要發生戰鬥(fight),每場戰鬥都是一個怪物與另一怪物之間的一對一戰鬥。每個怪物都有自己的速度(Speed)、生命值(hitpoint)、攻擊力值(damage)和防禦力值(defense);戰鬥時,兩個怪物依次攻擊對方,即怪物a首先攻擊怪物b,然後輪到怪物b攻擊怪物a,之後,怪物a再次攻擊怪物b,…,直到一方生命值為0;戰鬥時,由速度快的一方首先發起攻擊;若速度一樣,比較生命值,由高者首先攻擊;若生命值也相等,比較攻擊力,由高者首先攻擊;若攻擊力還相等,比較防禦力,由高者首先攻擊;若四項都相等,則選擇任一方首先攻擊;怪物A攻擊怪物B時,會給怪物B造成傷害,使得怪物B的生命值降低,降低值為:2*A的攻擊力-B的防禦力,最小為1。請根據你對上述描述的理解,定義並實現怪物類Monster,成員的設計可以任意,但要求該類至少有一個成員函式fight,用來描述與另外一個怪物進行戰鬥的過程。不必考慮怪物的生命值減少至0後如何處理。
怪物類:
class Monster {
private int speed;//速度
private int hitpoint;//生命值
private int damage;//攻擊力
private int defense;//防禦值
Monster(int spd,int hit,int dam,int def){
this.speed = spd;
this.hitpoint = hit;
this.damage = dam;
this.defense = def;
}
//進行戰鬥
boolean fight(Monster other){
if(priorTo(other)){ //若由己方攻擊
if(attacked(other) == 0){//敵方生命為0時戰鬥勝利,返回true
return true;
}
}
//由敵方發起,輪流攻擊,直至一方生命值為0
while(true){
if(other.attacked(this) == 0){
return false;
}
if (attacked(other) == 0){
return true;
}
}
}
//比較優先順序,若四項相同則由呼叫方攻擊
boolean priorTo(Monster other){
if(speed != other.speed){
return speed > other.speed;
}
if (hitpoint != other.hitpoint) {
return hitpoint > other.hitpoint;
}
if (damage != other.damage) {
return damage > other.damage;
}
if (defense != other.defense) {
return defense > other.defense;
}
return true;
}
//攻擊,返回被攻擊方的生命值
int attacked(Monster other){
int harm = damage * 2 - other.defense; //攻擊實際傷害
if(harm < 1){
harm = 1;
}
other.hitpoint -= harm; //生命值降低
if(other.hitpoint < 0){
other.hitpoint = 0;//生命值最低為0
}
return other.hitpoint;
}
}
測試類:
class TestMonster {
public static void main(String [] args){
Monster a = new Monster(10,200,7,8);
Monster b = new Monster(10,150,8,7);
if(a.fight(b)){
System.out.println("A win !");
}else{
System.out.println("B win !");
}
}
}
擴充套件:
或:
(3)雙向依賴
警察抓住犯人,犯人類中指明被哪個警察抓,這是一個雙向依賴。
class Police {
private int award;
Police(){
award = 0;
}
void catchPerson(Person person){};
}
class Person {
Person(){};
void beCatched(Police police){};
}
2.關聯關係
關聯(association)關係: 對於兩個相對獨立的物件,當一個物件的例項與另一個物件的一些特定例項存在固定的對應關係時,這兩個物件之間為關聯關係。
關聯關係體現的是兩個類、或者類與介面之間語義級別的一種強依賴關係,比如我和我的朋友;這種關係比依賴更強、不存在依賴關係的偶然性、關係也不是臨時性的,一般是長期性的,而且雙方的關係一般是平等的。
例如:客戶和訂單(1:N),公司和員工(1:N),主人和汽車(1:N),師傅和徒弟(1:N),丈夫和妻子(1:1),飛機和航班(1:N),學生和課程(N:N)。
class B {...}
class A {
B b;
afun() { b.bfun(); }
}
關聯關係的分類
關聯關係可以分為單向關聯,自身關聯和雙向關聯。
UML表示方法:實線箭頭,類A指向類B,表示單向關聯。如果使用雙箭頭或不使用箭頭表示雙向關聯。
(1)單向關聯
單向關聯是指只有某一方擁有另一方的引用,這樣只有擁有對方者可以呼叫對方的公共屬性和方法。
如下面程式碼:
class Husband
{
private int money;
public void goShopping();
};
class Wife
{
CHusband pHuband;
};
上面程式碼中妻子擁有丈夫,可以使用對方的屬性,比如錢,可以讓對方做能做的事,比如購物。
- 單向關聯例項:
學生管理程式中學生和宿舍。每個學生的資訊除了包括姓名、學號等之外,還要有宿舍資訊。宿舍資訊包括幾號樓,第幾層,幾號房間,以及住了哪幾個學生等資訊。
class Student
{
private Dorm mpDorm;
Student(Dorm aDorm){ }
int DormFloor(){
return mpDorm.floor( );
}
}
class Dorm {}
- 擴充套件:
class Parent{}
class A {
A{};
Parent p;
void func( );
}
(2)自身關聯
自身關聯是指擁有一個自身的引用。
例:連結串列中的節點類
class Node{
private int data;
private Node node;
Node(int data){
this.data = data;
}
}
(3)雙向關聯
雙向關聯是指雙方都擁有對方的引用,都可以呼叫對方的公共屬性和方法。
class Husband
{
private Wife wife;
}
class Wife
{
private Husband huband;
}
上面程式碼中丈夫和妻子是比較公平的關係,都可以使用對方公共的屬性。
3.聚合/聚集(aggregation)關係
當物件B被加入到物件A中,成為物件A的組成部分時,物件A和物件B之間為聚合關係。
聚合是關聯關係的一種特例。聚合指的是整體與部分之間的關係,體現的是整體與部分、擁有的關係,即has-a的關係,此時整體與部分之間是可分離的,可以具有各自的生命週期,部分可以屬於多個整體物件,也可以為多個整體物件共享。
例如:自行車和車把、響鈴、輪胎,汽車和引擎、輪胎、剎車裝置,計算機和主機板、CPU、記憶體、硬碟,航母編隊和航母、驅護艦艇、艦載機、潛艇,課題組和科研人員。
與關聯關係的比較:
-
在程式碼層面,聚合和關聯關係是一致的,只能從語義級別來區分。
-
關聯關係中兩個類是處於相同的層次,而聚合關係中兩個類是處於不平等的層次,一個表示整體,一個表示部分。
class B {…} class A { B b; …}
UML表示方法:尾部為空心菱形的實線箭頭(也可以沒箭頭),類A指向類B。
聚合示例:
學生宿舍管理程式中宿舍和學生。宿舍有多個學生,宿舍建立時,可以指定,也可不指定對應的多個學生,並且以後可以隨時增刪學生。
class Dorm {
private int maxCount;
Student []students;
Dorm(Student[] s,int count){
maxCount = count;
students = new Student[maxCount];
for (int i = 0; i <count ; i++) {
students[i] = s[i];
}
}
void addStudent(Student s , int index){
if(students[index] == null)
students[index] = s;
}
void removeStudent( int index){
students[index] = null;
}
}
拓展
簡單聚合有多種擴充套件。
- 擴充套件1:如果籃和水果
- 擴充套件2:如防盜門和鎖
- 擴充套件3:如防盜門和報警器
- 擴充套件4
- 擴充套件5
4.組合/合成(composition)關係
組合也是關聯關係的一種特例,體現的是一種contains-a的關係,這種關係比聚合更強,也稱為強聚合;同樣體現整體與部分間的關係,但此時整體與部分是不可分的,整體的生命週期結束也就意味著部分的生命週期結束。整體類負責部分類物件的生存與消亡。
例如:公司和部門,人和大腦、四肢,視窗和標題欄、選單欄、狀態列。
與關聯、聚合的區別:
在程式碼層面,組合和關聯關係是一致的,只能從語義級別來區分。
組合跟聚合幾乎相同,唯一的區別就是“部分”不能脫離“整體”單獨存在,就是說, “部分”的生命期不能比“整體”還要長。
class Company
{
Department department[n];
}
class Department
{
// Do something
}
上邊的例子中,部門是部分,公司是整體,部門不能脫離公司而存在,一旦公司破產了,部門也就不存在了,它們具有相同的生命週期。
UML表示方法:尾部為實心菱形的實現箭頭(也可以沒箭頭),類A指向類B
5.泛化(generalization)關係
泛化是一種一般與特殊、一般與具體之間關係的描述,具體描述建立在一般描述的基礎之上,並對其進行了擴充套件。
比如狗是對動物的具體描述,一般把狗設計為動物的子類。
在程式碼層面,泛化是通過繼承實現的。
class Animal{}
class Dog extends Animal{}
UML表示方法:空心三角形箭頭的實線,子類指向父類
6.實現(realization)關係
實現是一種類與介面的關係,表示類是介面所有特徵和行為的實現。從廣義上來說,類模板和模板類也是一種實現關係。
在程式碼層面,實現一般通過類實現介面來描述的。
Interface IAnimal{}
class Animal implements IAnimal
{
// Do something
}
UML表示方法:空心三角形箭頭的虛線,實現類指向介面。
泛化與實現的區別
泛化和實現的區別就在於子類是否繼承了父類的實現,如有繼承則關係為泛化,反之為實現。
總結
上述幾種關係所表現的強弱程度依次為:泛化/實現>組合>聚合>關聯>依賴,因此在程式設計的過程中,應根據業務需求選擇儘可能使用關聯程度弱的設計。