1. 程式人生 > 實用技巧 >設計模式之 UML 類圖(轉)

設計模式之 UML 類圖(轉)

轉自:https://zhuanlan.zhihu.com/p/24576502

前言

為什麼要學習設計模式?

個人覺得設計模式傳授的是一種思想,是一種脫離語言的程式設計習慣。對於一個沒有太多經驗的程式設計師,如何寫出簡潔優雅,可複用性高,可擴充套件性強,高內聚低耦合的程式碼至關重要。學習別人的設計模式就是為了在沒有經驗的情況下寫出一手不錯的程式碼,只看不寫並不能深刻體驗到設計模式的巧妙之處。

設計模式講的是別人千錘百煉出來的精華,如果有那麼一天你再次看設計模式覺得,這些沒什麼特別的,那麼說明你已經走上正軌,你的程式設計習慣已經向設計模式靠攏了。

這是我學習設計模式的開篇第一章,主要介紹下 UML 類圖。在之後的文章我也會介紹設計模式的一些基本原則以及 23 種設計模式,並給出詳細的程式碼說明。

UML 類圖

學習設計模式必定需要先讀懂 UML 類圖,下面就談談具體 UML 類圖中的概念。

首先是關於類本身,下面我以人為例,先看 UML 圖:

用 Java 程式碼可表示為:

class Student {
    private String name;
    public String getName() {
	return name;
    }
    public void takeExam(Course course) {
        course.test();
    }
}

class Course {
    private String courseName;
    public void test() {
        // take exam...
    }
}
  • 類名叫做 Student 和 Course
  • + 代表 public 公共;
    - 代表 private 私有;
    # 代表 protected;
    ~表示default,也就是包許可權;
    _下劃線表示static;
    斜體表示抽象。

  • 成員變數型別寫在前,引數名稱寫在後
  • 函式傳遞引數,引數名寫在前,型別寫在後
  • 函式返回值寫在函式簽名的後面
  • 兩個類之間若存在關係,可使用箭頭進行關聯,具體關聯規則在下文介紹
  • 箭頭上的數字代表 1 個學生可以不參加課程,也可以無限制參加各種課程
  • 1 代表一個,0..* 代表 0 個到無限個

在 Java 或其它面向物件設計模式中,類與類之間主要有 6 種關係,他們分別為:依賴,關聯,聚合,組合,繼承,實現。他們的耦合度依次增強。

1. 依賴 (Dependency)

依賴關係的定義為:對於兩個相對獨立的物件,當一個物件負責構造另一個物件的例項,或者依賴另一個物件的服務時,這兩個物件之間主要體現為依賴關係。

可以簡單的理解為:類 A 使用到了類 B,而這種使用關係具有偶然性,臨時性,非常弱的,但是 B 類中的變化會影響到類 A,比如某個學生要用筆寫字,學生與筆的關係就是一種依賴關係,如果筆沒水了,那學生就不能寫字了(B 類的變化會影響類 A) 或者換另一隻筆繼續寫字(臨時性體現)。

思考下面這樣的場景:

你是一名計程車司機,每天開著公司給你分配的車去載客,而每天計程車可能不同,我只是個司機,公司給我什麼車我就開什麼車,我使用這個車。

具體 UML 類圖表現為:

Java 程式碼:

class Driver {
    public void drive(Car car) {
	car.run();
    }
}
class Car {
    public void run(){}
} 

依賴關係不一定表現為形參,一共可以有三種表現形式:

class Driver {
    //通過形參方式發生依賴關係 
    public void drive1(Car car) {
	car.run();
    }
     //通過區域性變數發生依賴關係
    public void drive2() {
	Car car = new Car();
	car.run();
    }
    //通過靜態變數發生依賴關係
    public void drive3() {
	Car.run();
    }
}

2. 關聯 (Association)

關聯關係的定義為:對於兩個相對獨立的物件,當一個物件的例項與另一個物件的一些特定例項存在固定的對應關係時,這兩個物件之間為關聯關係。

它體現的兩個類中一種強依賴關係,比如我和我的朋友,這種關係比依賴更強,不存在依賴關係中的偶然性,關係也不是臨時的,一般是長期性的。

關聯關係分為單向關聯和雙向關聯:

  1. 在 Java 中,單向關聯表現為:類 A 當中使用了 類 B,其中類 B 是作為類 A 的成員變數。
  2. 雙向關聯表現為: 類 A 當中使用類 B 作為成員變數,同時類 B 中也使用了類 A 作為成員變數。

根據可以上面的例子可以修改為以下的場景:

我是一名老司機,車是我自己的,我擁有這輛車,平時也會用著輛車去載客人。

用 UML 類圖表示為:

雙向關聯的話箭頭可以省略。

用 Java 程式碼表示為:

class Driver {
    private Car car = new Car();
    public void drive() {
	car.run();
    }
}
class Car {
    public void run(){}
}

依賴和關聯區別:

  • 用錘子修了一下桌子,我和錘子之間就是一種依賴,我和我的同事就是一種關聯。
  • 依賴是一種弱關聯,只要一個類用到另一個類,但是和另一個類的關係不是太明顯的時候(可以說是“uses”了那個類),就可以把這種關係看成是依賴,依賴也可說是一種偶然的關係,而不是必然的關係。
  • 關聯是類之間的一種關係,例如老師教學生,老公和老婆這種關係是非常明顯的。依賴是比較陌生,關聯是我們已經認識熟悉了。

3.聚合 (Aggregation)

聚合關係是關聯關係的一種,耦合度強於關聯,他們的程式碼表現是相同的,僅僅是在語義上有所區別:關聯關係的物件間是相互獨立的,而聚合關係的物件之間存在著包容關係,他們之間是“整體-個體”的相互關係。

聚合關係中作為成員變數的類一般使用 set 方法賦值。

用 UML 類圖表示為:

Java 程式碼:

class Driver {
    private Car car = null;
    public void drive() {
	car.run();
    }
    public void setCar(Car c){
	car = c;
    }
}
class Car {
    public void run(){}
}

4. 組合 (Composition)

相比於聚合,組合是一種耦合度更強的關聯關係。存在組合關係的類表示“整體-部分”的關聯關係,“整體”負責“部分”的生命週期,他們之間是共生共死的;並且“部分”單獨存在時沒有任何意義。

對比與聚合關係,我們可以將前面的例子變為下面的場景:

  1. 車是一輛私家車,是司機財產的一部分,強調的是人財產的部分性,則相同的程式碼即可表示聚合關係。
  2. 車是司機必須有的財產,要想成為一個司機必須要現有財產,車要是沒了,司機也不想活了。而且司機要是不幹司機了,這車也就沒了。

所以,關聯、聚合、組合只能配合語義,結合上下文才能夠判斷出來,而只給出一段程式碼讓我們判斷是關聯,聚合,還是組合關係,則是無法判斷的。

用 UML 類圖表示為:

Java 程式碼:

class Driver {
    private Car car = null;
    public Driver(Car car) {
	this.car = car;
    }
    public void drive() {
	car.run();
    }
}
class Car {
    public void run(){}
}

再舉一個恰當點的例子:

人和靈魂,身體之間是組合關係,當人的生命週期開始時,必須同時擁有靈魂和肉體,當人的生命週期結束時,靈魂肉體隨之消亡;無論是靈魂還是肉體,都不能單獨存在,他們必須作為人的組成部分存在。

用 UML 類圖表示為

5. 繼承 (Generalization)

繼承表示類與類 (或者介面與介面) 之間的父子關係。在 Java 中,用關鍵字 extends 表示繼承關係。

思考下面的場景:

人是一種高階動物,不僅可以像動物可以吃和睡覺,而且還可以學習。

用 UML 類圖表示為:

Java 程式碼表示為:

class Animal {
    public void eat(){}
    public void sleep(){}
}
class People extends Animal {
    public void study(){}
}

6. 實現 (Implementation)

表示一個類實現一個或多個介面的方法。介面定義好操作的集合,由實現類去完成介面的具體操作, 在 Java 中使用 implements 表示。在 Java 中,如果實現了某個介面,那麼就必須實現介面中所有的方法。

比如一個人可以吃飯和學習,那麼就可以定義一個人的介面。讓具體的人去實現它。

用 UML 類圖表示為:

Java 程式碼:

interface IPeople {
    public void eat();
    public void study();
}
class People implements IPeople {
    public void eat(){
    }
    public void study(){
    }
}