我是技術搬運工,好東西當然要和大家分享啦.原文地址
S.O.L.I.D
S.O.L.I.D是面向對象設計和編程(OOD&OOP)中幾個重要編碼原則(Programming Priciple)的首字母縮寫。
簡寫 | 全拼 | 中文翻譯 |
---|---|---|
SRP | The Single Responsibility Principle | 單一責任原則 |
OCP | The Open Closed Principle | 開放封閉原則 |
LSP | The Liskov Substitution Principle | 里氏替換原則 |
ISP | The Interface Segregation Principle | 接口分離原則 |
DIP | The Dependency Inversion Principle | 依賴倒置原則 |
1. 單一責任原則
當需要修改某個類的時候原因有且只有一個。換句話說就是讓一個類只做一種類型責任,當這個類需要承當其他類型的責任的時候,就需要分解這個類。
2. 開放封閉原則
軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。
3. 里氏替換原則
當一個子類的實例應該能夠替換任何其超類的實例時,它們之間才具有 is-a 關係。
4. 接口分離原則
不能強迫用戶去依賴那些他們不使用的接口。換句話說,使用多個專門的接口比使用單一的總接口總要好。
5. 依賴倒置原則
- 高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象
- 抽象不應該依賴於細節,細節應該依賴於抽象
封裝、繼承、多態
封裝、繼承、多態是面向對象的三大特性。
1. 封裝
利用抽象數據類型將數據和基於數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,儘可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯繫。用戶是無需知道對象內部的細節,但可以通過該對象對外的提供的接口來訪問該對象。
封裝有三大好處:
良好的封裝能夠減少耦合。
類內部的結構可以自由修改。
可以對成員進行更精確的控制。
隱藏信息,實現細節。
以下 Person 類封裝 name、gender、age 等屬性,外界只能通過 get() 方法獲取一個 Person 對象的 name 屬性和 gender 屬性,而無法獲取 age 屬性,但是 age 屬性可以供 work() 方法使用。
注意到 gender 屬性使用 int 數據類型進行存儲,封裝使得用戶注意不到這種實現細節。並且在需要修改使用的數據類型時,也可以在不影響客戶端代碼的情況下進行。
public class Person {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public String getGender() {
return gender == 0 ? "man" : "woman";
}
public void work() {
if(18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
System.out.println(name + " can't work!");
}
}
}
2. 繼承
繼承實現了 is-a 關係,例如 Cat 和 Animal 就是一種 is-a 關係,因此可以將 Cat 繼承自 Animal,從而獲得 Animal 非 private 的屬性和方法。
Cat 可以當做 Animal 來使用,也就是可以使用 Animal 引用 Cat 對象,這種子類轉換爲父類稱爲 向上轉型。
繼承應該遵循里氏替換原則:當一個子類的實例應該能夠替換任何其超類的實例時,它們之間才具有 is-a 關係。
Animal animal = new Cat();
3. 多態
多態分爲編譯時多態和運行時多態。編譯時多態主要指方法的重裝,運行時多態指程序中定義的對象引用所指向的具體類型在運行期間才確定。
多態有三個條件:1. 繼承;2. 覆蓋父類方法;3. 向上轉型。
下面的代碼中,樂器類(Instrument)有兩個子類:Wind 和 Percussion,它們都覆蓋了 play() 方法,並且在 main() 方法中使用父類 Instrument 來引用 Wind 和 Percussion 對象。在 Instrument 引用調用 play() 方法時,會執行實際引用對象所在類的 play() 方法,而不是 Instrument 類的方法。
public class Instrument {
public void play() {
System.out.println("Instument is playing...");
}
}
public class Wind extends Instrument {
public void play() {
System.out.println("Wind is playing...");
}
}
public class Percussion extends Instrument {
public void play() {
System.out.println("Percussion is playing...");
}
}
public class Music {
public static void main(String[] args){
List<Instrument> instruments = new ArrayList<>();
instruments.add(new Wind());
instruments.add(new Percussion());
for(Instrument instrument : instruments){
instrument.play();
}
}
}
UML
1. 類圖
1.1 繼承相關
繼承有兩種形式: 泛化(generalize)和實現(realize),表現爲 is-a 關係。
① 泛化關係(generalization)
從具體類中繼承
② 實現關係(realize)
從抽象類或者接口中繼承
1.2 整體和部分
① 聚合關係(aggregation)
表示整體由部分組成,但是整體和部分不是強依賴的,整體不存在了部分還是會存在。以下表示 B 由 A 組成:
② 組合關係(composition)
和聚合不同,組合中整體和部分是強依賴的,整體不存在了部分也不存在了。比如公司和部門,公司沒了部門就不存在了。但是公司和員工就屬於聚合關係了,因爲公司沒了員工還在。
1.3 相互聯繫
① 關聯關係(association)
表示不同類對象之間有關聯,這是一種靜態關係,與運行過程的狀態無關,在最開始就可以確定。因此也可以用 1 對 1、多對 1、多對多這種關聯關係來表示。比如學生和學校就是一種關聯關係,一個學校可以有很多學生,但是一個學生只屬於一個學校,因此這是一種多對一的關係,在運行開始之前就可以確定。
② 依賴關係(dependency)
和關聯關係不同的是, 依賴關係是在運行過程中起作用的。一般依賴作爲類的構造器或者方法的參數傳入。雙向依賴時一種不好的設計。
2. 時序圖
2.1 定義
時序圖描述了對象之間傳遞消息的時間順序,它用來表示用例的行爲順序。它的主要作用是通過對象間的交互來描述用例(注意是對象),從而尋找類的操作。
2.2 赤壁之戰時序圖
從虛線從上往下表示時間的推進。
可見,通過時序圖可以知道每個類具有以下操作:
publc class 劉備 {
public void 應戰();
}
publc class 孔明 {
public void 擬定策略();
public void 聯合孫權();
private void 借東風火攻();
}
public class 關羽 {
public void 防守荊州();
}
public class 張飛 {
public void 防守荊州前線();
}
public class 孫權 {
public void 領兵相助();
}
2.3 活動圖、時序圖之間的關係
活動圖示從用戶的角度來描述用例;
時序圖是從計算機的角度(對象間的交互)描述用例。
2.4 類圖與時序圖的關係
類圖描述系統的靜態結構,時序圖描述系統的動態行爲。
2.5 時序圖的組成
① 對象
有三種表現形式
在畫圖時,應該遵循以下原則:
把交互頻繁的對象儘可能地靠攏。
把初始化整個交互活動的對象(有時是一個參與者)放置在最左邊。
② 生命線
生命線從對象的創建開始到對象銷燬時終止
③ 消息
對象之間的交互式通過發送消息來實現的。
消息有4種類型:
1. 簡單消息,不區分同步異步。
2. 同步消息,發送消息之後需要暫停活動來等待迴應。
3. 異步消息,發送消息之後不需要等待。
4. 返回消息,可選。
④ 激活
生命線上的方框表示激活狀態,其它時間處於休眠狀態。