設計模式
什麼是設計模式?
他是一套提高代碼複用性,健壯性,可讀性,安全性的方案
設計模式的分類
- 按目的
- 按作用範圍
23中設計模式簡介
-
Singleton-單例模式:某個類只能生成一個對象。
-
Prototype-原型模式:把某個對象作爲原型,對其進行復制從而創建出多個與該原型類似新新對象
-
Factory Method-工廠方法模式:定義一個創建對象的接口,具體生成什麼對象,由子類來實現
-
Abstract Factory-抽象工廠模式:提供一個創建對象族的接口,每個子類可以生成一系列的對象
-
Builder-建造者模式:將一個複雜的對象分解成多個相對簡單的部分,根據不同的需求去創建他們,最後構成該複雜對象
-
Proxy-代理模式:爲對象提供一個種代理來控制對該對象的訪問
-
Bridge-橋接模式:將抽象與實現分離,使他們可以單獨變化,降低抽象與實現的耦合度
-
Adapter-適配器模式:創建一個新類(即適配器類)使得原來接口不兼容不能一起工作的類能一起工作
-
Dectorator-裝飾器模式:動態的爲對象添加額外的功能
-
Factor-外觀模式:爲多個複雜的子系統提供一個一致的接口,使得這些子系統更加容易被訪問
-
FlyWeight-享元模式:通過共享技術有效的支持大量細粒度對象的複用
-
Composite-組合模式:將對象組合成樹狀層次結構,使得單個對象和組合對象的訪問具有一致性
-
TemplateMethod-模板方法模式:定義一個算法框架,將算法的一些步驟延遲到子類,子類可以在不改變該算法結構的前提下,重寫算法的一些步驟
-
Strage-策略模式:定義一組算法,並將每個算法封裝,使得他們可以自由切換。
-
Command-命令模式:將請求封裝成一個對象,使得發出請求的責任與執行請求的責任分隔開。
-
Chain-of-Reposibility-責任鏈模式:將請求從一個對象傳遞到另一個對象,知道請求被響應,以此來降低對象間的耦合度。
-
State-狀態模式:一個對象在內部狀態改變時改變能夠其行爲。
-
Observer-觀察者模式:多個對象間存在一對多的關係,當其中一種對象發生改變時,能夠將改變通知給其他對象,從而影響其他對象的行爲。
-
Mediator-中介者模式:通過定義一箇中介對象來簡化原有對象之間的交互關係,降低對象間的耦合度。
-
Iterator-迭代器模式:提供了一種方法來訪問聚合對象中的一系列數據,而不暴露元素的內部表示
-
Visitor-訪問者模式:在不訪問集合元素的前提下,爲元素提供多種訪問方式,即每個元素有多個訪問者對象訪問。
-
Memento-備忘錄模式:在不破壞對象封裝性的前提下,訪問並保存對象的內部狀態,在需要的時候恢復對象。
-
Interpreter-解釋器模式:提供瞭如何定義語言的方法,以及如何解釋語言子句的方法。
範圍\目的 | 創建型模式 | 結構型模式 | 行爲型模式 |
---|---|---|---|
類模式 | 工廠方法 | (類)適配器 | 模板方法 解釋器 |
對象模式 | 單例 原型 抽象工廠 建造者 |
代理 (對象)適配器 橋接 裝飾 外觀 享元 組合 |
策略 命令 職責鏈 狀態 觀察者 中介者 迭代器 訪問者 備忘錄 |
類之間的關係
依賴關係
是一種使用關係,是臨時性的關聯,耦合度最低的一種關係
表現形式
- 局部變量
- 方法參數
- 靜態方法調用
表示方法:帶箭頭的虛線
關聯關係
用來表示對象之間的聯繫,如丈夫-妻子,老師-學生,可以是單向也可以是雙向的
表現形式:一個類的對象作爲另一個類的成員變量,即成員對象的形式
- 一般關聯關係
- 表示對象間的聯繫
- 表示方法:帶箭頭的實線
- 聚合關係
- 表示對象間是部分與整體的關係,是has-a關係,但是成員對象可以脫離整體對象而獨立存在,如學校與老師的關係,學校停辦了,老師依舊存在
- 表示方法:帶空心菱形的實線
- 組合關係
- 也是表示對象間是部分與整體的關係,是cxmtains-a的關係,整體對象可以控制成員對象的生命週期,但是成員對象不可以脫離整體對象而獨立存在,如頭和嘴的關係,沒有了頭,嘴就不存在了
- 表示方法:帶實心菱形的實線
泛化關係
類與類之間耦合度最高一種關係,表示一般與特殊的關係
-
表現形式:java的繼承
-
表示方法:帶空心三角的實線
實現關係
即接口與實現類之間的關係
- 表示方法:帶空心三角的虛線
總結:沒有帶實心三角的,哈哈
7條常見的原則
口訣:開理依,單結(結)合體(迪)
開閉原則(Open Closed Principle,OCP)
定義:當應用需求發生改變時,在不修改源代碼或二進制代碼的前提下,擴展模塊功能
核心內容:軟件實體應該對擴展開放,對修改封閉
軟件實體分爲三部分
- 模塊
- 類與接口
- 方法
實現方式:可以通過“抽象約束,封裝變化”的方式來實現開閉原則,通過接口或抽象類爲接口定義一個相對穩定的抽象層,而可變因素則是封裝在具體的實現類中
作用:
- 對軟件測試的影響:只需要測試拓展的代碼即可
- 提高代碼的可複用性:粒度越小,可複用性越大
- 提高軟件的可維護性和穩定性
里氏替換原則(Liskov Substitution Principle,LSP,Liskov:里斯科夫)
定義:繼承必須保證基類擁有的特性在派生類仍然成立(像不敢改革的領導)
核心內容:指明瞭什麼時候應該使用繼承,什麼時候不該使用繼承,子類可以擴展父類的功能,但是不能修改父類的功能,如企鵝,鴕鳥從生物學上講他們都是鳥類,但是他們不能繼承鳥類會“飛”的功能。
實現方式:
- 子類可以擴展父類的功能,但不能修改父類的功能
作用:
- 里氏替換是實現開閉原則的重要方式之一
- 克服了繼承中重寫父類造成可複用性變差的缺點
- 類的擴展不會給現有系統引入新的錯誤,即降低代碼出錯的可能性
依賴倒置原則(Dependence Inversion Principle,DIP)
定義:高層模塊不應該依賴於底層模塊,兩者都應該依賴於其抽象,因爲抽象不依賴於其細節,而細節卻依賴於抽象。
核心內容:要面向接口編程,不應該面向實現編程,他是開閉原則的重要實現之一,降低了客戶和實現模塊之間的耦合度
實現方式:
- 每個類儘量提供接口或抽象類,或者兩者都具備。
- 變量的聲明類型儘量是接口或者是抽象類。
- 任何類都不應該從具體類派生。
- 使用繼承時儘量遵循里氏替換原則。
作用:
- 降低類之間的耦合度
- 提高代碼的可讀性和可維護性
- 提高系統的穩定性
- 減少並行開發引起的風險(因爲接口和抽象類寫好了契約)
例子:
interface Shop {
void selling();
}
class BeiJingShop implements Shop{
@Override
public void selling() {
System.out.println("北京特產:北京烤鴨,北京酥糖");
}
}
class ShangHaiShop implements Shop {
@Override
public void selling() {
System.out.println("上海特產:上海水蜜桃,上海浦東雞");
}
}
class Cumtomer {
public void shopping(Shop shop) {
shop.selling();
}
}
public class DIP {
public static void main(String[] args) {
BeiJingShop beiJingShop = new BeiJingShop();
ShangHaiShop shangHaiShop = new ShangHaiShop();
Cumtomer cumtomer = new Cumtomer();
System.out.println("顧客購買以下商品");
cumtomer.shopping(beiJingShop);
cumtomer.shopping(shangHaiShop);
}
}
//output
顧客購買以下商品
北京特產:北京烤鴨,北京酥糖
上海特產:上海水蜜桃,上海浦東雞
單一職責原則(Single Responsibility Principle,SRP,又稱單一功能原則)
定義:一個類應該有且只有一個引起他變化的原因,否則這個類應該被拆分(即拆分類)
如果一個類承擔了太多職責的缺點:
- 某個職責的變化可能會抑制這個類實現其他職責的能力
- 客戶端只需要某個職責時,不得不將其他職責包含進來,造成冗餘代碼
實現方式:
- 根據職責拆分類
作用:
- 降低代碼的複雜度,一個類只負責一個職責
- 提高提高代碼的可讀性和可維護性:可讀性提高了,可維護性也提高了
- 變更引起的風險降低,變更是必然的,當修改一個功能時,可以顯著的降低對其他功能的影響
例子:學生工作中:生活由輔導員管理,而學業由指導老師管,而不是都丟給其中一個老師
接口隔離原則(Interface Segregation Principle,ISP)
定義:一個類對另一個類的依賴應該建立在最小的接口上(即儘量拆分接口)
實現方式:
- 接口儘量小,但要有限度,一個接口只服務於一個子模塊
- 瞭解環境,拒絕盲從,環境不同,接口拆分的原則不同,需要深入瞭解業務邏輯
- 爲依賴接口的類定製方法,只提供調用者需要的方法
作用:
- 將臃腫的接口拆分成粒度小的接口,提高系統的靈活性
- 降低代碼的冗餘度
- 提高系統的可讀性和可維護性
例子:學生成績管理程序,分三個接口:錄入模塊,計算模塊,打印模塊
合成複用原則(Composite Reuse Principle,CRP)
合成複用又稱組合/聚合複用
通常類的複用分:繼承複用與組合/聚合複用
繼承複用的缺點:
- 父類子類耦合度高,限制了類的擴展性,父類的任何修改都會導致子類發生發生變化
- 降低了靈活度,父類繼承來的實現都是編譯時已確定
- 破壞了類的封裝性,繼承會把父類的實現細節暴露給子類
合成複用的優點:
- 維護了封裝性,成員對象的內部細節是不可見的
- 耦合度降低,整體對象調用成員對象的唯一方法是通過成員對象提供的接口
- 提高了靈活度,動態的指定成員對象
例子:汽車分類管理程序
迪米特法則(Law of Demeter,LoD)
又名:最少知識原則
定義:如果兩個軟件實體不發生直接通信,則不應該產生直接相互調用,而應該通過第三方轉發調用
作用:
- 降低耦合度(變成“一般關聯”關係,而不是“泛化”關係),提高模塊的獨立性
- 提高了獨立性意味着可複用性提高
強調的點:
- 從依賴者的角度,只依賴該依賴的對象
- 從被依賴者的角度,只暴露該暴露的方法
運用法則的注意事項:
- 應當創建弱耦合的類
- 降低成員變量的訪問權限
- 需私有成員的提供訪問方法
- 慎重使用序列化
例子:助理,明星,粉絲,影視公司
class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Fan {
private String name;
public Fan(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Agent {
private Star star;
private Company company;
private Fan fan;
public void setStar(Star star) {
this.star = star;
}
public void setCompany(Company company) {
this.company = company;
}
public void setFan(Fan fan) {
this.fan = fan;
}
public void meeting() {
System.out.println(star.getName()+" 與 "+fan.getName()+" 見面");
}
public void business() {
System.out.println(star.getName()+" 與 "+company.getName()+" 合作");
}
}
public class LOD {
public static void main(String[] args) {
Agent agent = new Agent();
agent.setCompany(new Company("唐人影視有限公司"));
agent.setFan(new Fan("曾小哥"));
agent.setStar(new Star("唐仁"));
agent.business();
agent.meeting();
}
}
//output
唐仁 與 唐人影視有限公司 合作
唐仁 與 曾小哥 見面