1. 程式人生 > >一文總結設計模式

一文總結設計模式

# 前言 - 看了很多寓教於學寫設計模式的,看的有點頭疼,注意力全都在故事上了,滿腦子都是鴨子,餐廳之類,還有一堆和設計模式不相關的話,翻書都翻的挺累的。 - 這裡我整理了下23種設計模式,`沒什麼多餘的話,程式碼演示,簡單粗暴`,借鑑的地方都附上了參考連結(做個優秀的搬運工),沒附上的是自己總結的。 - 借鑑的例子程式碼,基本都做了一些精簡,`如果相關例子寫的有什麼不準確,麻煩在評論裡面指出來,最好附上程式碼,我會盡快修改文章中的相關例項`。 - 23種設計模式,一文呈現,方便大家和自己查詢,也方便自己隨時修改,請配合文章旁邊的大綱食用。 # 總述 ## 7種面向物件設計原則 | **設計原則名稱** | **定 義** | | -------------------------------------------------- | ------------------------------------------------ | | 單一職責原則(Single Responsibility Principle, SRP) | 一個類只負責一個功能領域中的相應職責 | | 開閉原則(Open-Closed Principle, OCP) | 軟體實體應對擴充套件開放,而對修改關閉 | | 里氏代換原則(Liskov Substitution Principle, LSP) | 所有引用基類物件的地方能夠透明地使用其子類的物件 | | 迪米特法則(Law of Demeter, LoD) | 一個軟體實體應當儘可能少地與其他實體發生相互作用 | | 介面隔離原則(Interface Segregation Principle, ISP) | 使用多個專門的介面,而不使用單一的總介面 | | 依賴倒轉原則(Dependence Inversion Principle, DIP) | 抽象不應該依賴於細節,細節應該依賴於抽象 | | 合成複用原則(Composite Reuse Principle, CRP) | 儘量使用物件組合,而不是繼承來達到複用的目的 | - 設計模式可分為建立型(Creational),結構型(Structural)和行為型(Behavioral)三種 - 建立型模式主要用於描述如何建立物件(5種) - 結構型模式主要用於描述如何實現類或物件的組合(7種) - 行為型模式主要用於描述類或物件怎樣互動以及怎樣分配職責(11種) - 圖解 ![image](https://cdn.jsdelivr.net/gh/CNAD666/MyData/pic/thought/20201123103552.png) ## 原則簡述 ### 1. 單一職責原則 **定義**:一個類只有一個引起它變化的原因。 **理解**:對功能進行分類,程式碼進行解耦,一個類只管一件事 **舉例**:就比如一個網路請求框架大體分為:請求類,快取類,配置類,不能把這3個混在一起,必須分為3個類去實現不同的功能。 ### 2.開閉原則 **定義**:一個實體(類、函式、模組等)應該對外擴充套件開放,對內修改關閉 **理解**:每次發生變化時,要通過新增程式碼來增強現有型別的行為,而不是修改原有程式碼。 **舉例**:就比如在軟體的生命週期內,因為產品迭代,軟體升級維護等原因,需要對原有程式碼進行修改時,可能會給原有程式碼引入錯誤,也可能使得我們對整個功能不得不進行重構,並且需要對原有程式碼進行重新測試,這樣的話,對開發週期影響很大,所以開閉原則剛好解決這個問題。 ### 3. 里氏替換原則 **定義**:繼承必須確保超類所擁有的性質在子類中仍然成立。 **理解**:在繼承類時,除了擴充套件一些新的功能之外,儘量不要刪除或者修改對父類方法的引用,也儘量不要過載父類的方法。 **舉例**:你看啊,就比如Object有個方法,叫equals,如果不遵守里氏代替原則,它的子類過載了equals這個方法,並且返回了個null,這個子類的下一個繼承者也會返回null,那麼,在不同開發人員開發時,可能考慮不到這個問題,那麼就可能導致程式崩潰。 ### 4.迪米特法則 **定義**:一個模組或物件應儘量少的與其他實體之間發生相互作用,使得系統功能模組相對獨立,這樣當一個模組修改時,影響的模組就會越少,擴充套件起來更加容易。 **理解**:一個物件應該對其他物件有最少的瞭解;一個類應該對自己需要耦合或呼叫的類知道得最少,類的內部如何實現、如何複雜都與呼叫者或者依賴者沒關係,呼叫者或者依賴者只需要知道他需要的方法即可,其他的一概不關心。類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。 **舉例**:一般在使用框架的時候,框架的開發者會抽出一個類供外部呼叫,而這個主要的類像是一箇中介一樣去呼叫框架裡面的其他類,恰恰框架裡面其他類一般都是不可訪問(呼叫)的,這個框架就遵守了迪米特原則,其他開發人員只關心呼叫的方法,並不需要關心功能具體如何實現。 ### 5.介面隔離原則 **定義**:使用多個專門功能的介面,而不是使用單一的總介面 **理解**:在定義介面方法時應該合理化,儘量追求簡單最小,避免介面臃腫 **舉例**:在實際開發中,往往為了節省時間,可能會將多個功能的方法抽成一個介面,其實這設計理念不正確的,這樣會使介面處於臃腫的狀態,這時就需要合理的拆分介面中的方法,另外抽取成一個獨立的介面,避免原有的介面臃腫導致程式碼理解困難。 ### 6.依賴倒置原則 **定義**:細節應該依賴於抽象,而抽象不應該依賴於細節 **理解**:高層模組不依賴低層次模組的細節,不依賴具體的類,而是依賴於介面 **舉例**:比如說我們寫一個網路框架,為了滿足不同開發者的需求,即能使用高效的OkHttp框架,也可以使用原生的API。那麼是如何進行切換的呢,這個時候需要面向介面程式設計思想了,把一些網路請求的方法封裝成一個介面,然後分別建立OkHttp和原生API的介面實現類,當然也可以擴充套件其他網路框架的應用。 ### 7.合成複用原則 **定義**:儘量使用物件組合,而不是繼承來達到複用的目的。 **理解**:它要求在軟體複用時,要儘量先使用組合或者聚合等關聯關係來實現,其次才考慮使用繼承關係來實現。 **舉例**:相當於我們開發軟體,一個模組的構造,就像搭積木一樣,通過組合的形式,完成整體的構建;現在宣告式UI框架,對於這種思想比較貫徹。 **參考** - https://blog.csdn.net/lovelion/article/details/7536532 - https://juejin.im/post/5d8861f45188250970131f41 # 建立型模式 ## 單例模式 **餓漢模式** ```java public class Singleton { // 只會例項化一次 private static Singleton instance = new Singleton(); // 私有構造方法,防止被例項化 private Singleton() {} public static Singleton getInstance() { return instance; } } ``` - 在未使用該類的時候,也會例項化物件,會造成資源浪費,除非在一定會用到該種類的場景,否則不建議使用。 **懶漢模式** ```java public class Singleton { // 持有私有靜態例項,防止被引用;賦值為null,目的是實現延遲載入;volatile修飾是禁止重排 private volatile static Singleton instance = null; // 私有構造方法,防止被例項化 private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` - 使用volatile修飾,可禁止重排,在多執行緒場景也能保證正常執行;只會在使用的時候,才會去例項化物件,一般場景都可以使用。 **靜態內部類模式** ```java public class Singleton { // 私有構造方法,防止被例項化 private Singleton() {} // 獲取例項 public static Singleton getInstance() { return Inner.instance; } // 使用一個內部類來維護單例,只有在該類被載入的時候,才會例項化物件 private static class Inner { private static Singleton instance = new Singleton(); } } ``` - 餓漢模式升級版,解決了資源浪費問題,同時也能保證執行緒安全;只有在內部類被載入的時候,才會去例項化物件;相對於懶漢模式,靜態內部類的方式避免了排隊進同步程式碼塊做null的判斷,效能優於懶漢模式,推薦使用。 **列舉模式** ```java public enum Singleton { // 建立一個列舉物件,該物件天生為單例 INSTANCE; public Singleton getInstance(){ return INSTANCE; } } ``` - 推薦:effective java中最佳的單例實現模式就是列舉模式,JVM來幫我們保證執行緒安全和單一例項,在反射和序列化的場景中,仍能保證單一例項。 **參考** - https://blog.csdn.net/zhangerqing/article/details/8194653 - https://www.cnblogs.com/happy4java/p/11206105.html ## 工廠模式 ### 簡單工廠模式 - 以手機為例來說明 - 定義一個介面,開機的時候,手機介面上,會顯示該手機系統 ```java public interface Phone { //實施開機操作的時候,返回手機系統 String startUp(); } ``` - 實現一個Android手機類,實現Phone介面,返回“Android”手機系統 ```java public class AndroidPhone implements Phone { @Override public String startUp() { return "Android"; } } ``` - 實現一個IOS手機類 ```java public class IOSPhone implements Phone { @Override public String startUp() { return "IOS"; } } ``` - 建立手機工廠管理類 ```java public class PhoneFactory { private static Phone mPhone; //根據系統關鍵字獲取相應手機物件 public static Phone createPhone(String system) { switch (system) { case "Android": mPhone = new AndroidPhone(); break; case "IOS": mPhone = new IOSPhone(); break; } return mPhone; } } ``` - 使用 ```java public void test() { Phone android = PhoneFactory.createPhone("Android"); Phone ios = PhoneFactory.createPhone("IOS"); System.out.print(android.startUp() + "\n"); System.out.print(ios.startUp() + "\n"); } ``` - 結果顯示:Android IOS ### 工廠模式 - 工廠模式區別與簡單工廠模式的是,工廠模式不是用一個統一的工廠類來管理所有物件,而是每一個物件都有不同的工廠相對應。 - 繼續以手機為例說明,需要針對Android和IOS手機這倆個實現類,去構建不同的工廠,這邊用介面去規範不同的工廠類 - 手機工廠介面 ```java public interface PhoneFactory { //獲取相關手機例項 Phone getPhone(); } ``` - 建立Android手機工廠 ```java public class AndroidPhoneFactory implements PhoneFactory { @Override public Phone getPhone() { return new AndroidPhone(); } } ``` - 建立IOS手機工廠 ```java public class IOSPhoneFactory implements PhoneFactory { @Override public Phone getPhone() { return new IOSPhone(); } } ``` - 使用 ```java public void test() { PhoneFactory androidMiFactory = new AndroidPhoneFactory(); PhoneFactory iosFactory = new IOSPhoneFactory(); System.out.print(androidFactory.getPhone().startUp() + "\n"); System.out.print(iosFactory.getPhone().startUp() + "\n"); } ``` - 結果顯示:Android IOS ## 抽象工廠模式 - 抽象模式是對工廠模式的進一步優化,工廠類不單單隻能建立一個物件,而是能建立一組物件。 - 在這裡,大家可能存在一個疑惑,簡單工廠不也是建立一組物件嗎?是的,在這點上,倆者是非常非常相似的,區別在於:簡單工廠模式是外部傳值進去,以獲取不同物件;抽象工廠模式直接通過方法獲取物件,不需要傳值。 - 繼續以手機為例說明,咱們用倆種方式來看看,中間工廠類來建立一組物件 **1、第一種方式** - 定義一個介面,這個接口裡面是獲取一系列的手機系統的例項 ```java public interface PhoneSystem { //獲取Android手機例項 Phone getAndroid(); //獲取IOS手機例項 Phone getIOS(); } ``` - 實現抽象工廠的這個介面 ```java public class PhoneFactory implements PhoneSystem { @Override public Phone getAndroid() { return new AndroidPhone(); } @Override public Phone getIOS() { return new IOSPhone(); } } ``` - 使用 ```java public void test() { PhoneSystem phoneSystem = new PhoneFactory(); Phone android = phoneSystem.getXiaoMi(); Phone ios = phoneSystem.getHuaWei(); System.out.print(android.startUp() + "\n"); System.out.print(ios.startUp() + "\n"); } ``` - 結果顯示:Android IOS **2、第二種方式** - 在這裡思考下,抽象工廠模式,是在工廠類裡面建立一組物件,與外層互動,不是通過關鍵字去返回相應物件,而是通過某個共性方法去返回“符合條件的例項”。 - 這裡假設一個場景:我們定義的手機物件,其中的開機功能,只在對應的手機上才會起作用(Android手機想開機,只能使用Android手機類中的開機方法,IOS也是如此),在這裡,假設此款手機是Android手機。 - 此處,我們不定義介面,直接建立抽象工廠類。 ```java public class PhoneFactory { private static Phone instance; //模擬個數據 private String SYSTEM = "IOS"; public static Phone getInstance() { if("Android".equals(SYSTEM)) { return new AndroidPhone(); } if("IOS".equals(SYSTEM)) { return new IOSPhone(); } return null; } } ``` - 使用 ```java public static void test() { Phone phone = PhoneFactory.getInstance(); if (phone == null) return; System.out.print(phone.startUp() + "\n"); } ``` - 結果顯示:IOS ## 建造者模式 **說明** 1. 在Computer 中建立一個靜態內部類 Builder,然後將Computer 中的引數都複製到Builder類中。 2. 在Computer中建立一個private的建構函式,引數為Builder型別 3. 在Builder中建立一個public的建構函式,引數為Computer中必填的那些引數,cpu 和ram。 4. 在Builder中建立設定函式,對Computer中那些可選引數進行賦值,返回值為Builder型別的例項 5. 在Builder中建立一個build()方法,在其中構建Computer的例項並返回 **實現** ```java public class Computer { private final String cpu;//必須 private final String ram;//必須 private final int usbCount;//可選 private final String keyboard;//可選 private final String display;//可選 private Computer(Builder builder){ this.cpu=builder.cpu; this.ram=builder.ram; this.usbCount=builder.usbCount; this.keyboard=builder.keyboard; this.display=builder.display; } public static class Builder{ private String cpu;//必須 private String ram;//必須 private int usbCount;//可選 private String keyboard;//可選 private String display;//可選 public Builder(String cup,String ram){ this.cpu=cup; this.ram=ram; } public Builder setUsbCount(int usbCount) { this.usbCount = usbCount; return this; } public Builder setKeyboard(String keyboard) { this.keyboard = keyboard; return this; } public Builder setDisplay(String display) { this.display = display; return this; } public Computer build(){ return new Computer(this); } } } ``` **使用** ```java Computer computer = new Computer.Builder("因特爾","三星") .setDisplay("三星24寸") .setKeyboard("羅技") .setUsbCount(2) .build(); ``` **參考** - https://zhuanlan.zhihu.com/p/58093669 ## 原型模式 **定義** - 原型模式(Prototype Pattern):使用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。原型模式是一種物件建立型模式。 **實現** ```java public class Prototype implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private String string; private SerializableObject obj; // 淺複製 public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } // 深複製 public Object deepClone() throws IOException, ClassNotFoundException { // 寫入當前物件的二進位制流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 讀出二進位制流產生的新物件 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getString() { return string; } public void setString(String string) { this.string = string; } public SerializableObject getObj() { return obj; } public void setObj(SerializableObject obj) { this.obj = obj; } } class SerializableObject implements Serializable { private static final long serialVersionUID = 1L; } ``` **參考** - https://blog.csdn.net/zhangerqing/article/details/8194653 # 結構型模式 ## 介面卡模式 ### 類適配 - 有一個已存在的將被適配的類 ```java public class Adaptee { public void adapteeRequest() { System.out.println("被適配者的方法"); } } ``` - 定義一個目標介面 ```java public interface Target { void request(); } ``` - 怎麼才可以在目標介面中的 request() 呼叫 Adaptee 的 adapteeRequest() 方法呢? - 通過一個介面卡類,實現 Target 介面,同時繼承了 Adaptee 類,然後在實現的 request() 方法中呼叫父類的 adapteeRequest() 即可實現 ```java public class Adapter extends Adaptee implements Target{ @Override public void request() { System.out.println("concreteTarget目標方法"); super.adapteeRequest(); } } ``` - 測試一下 ```java public class Test { public static void main(String[] args) { Target adapterTarget = new Adapter(); adapterTarget.request(); } } ``` - 輸出 ```java concreteTarget目標方法 被適配者的方法 ``` ### 物件適配 - 電源介面卡:定義輸出交流電介面,輸出220V交流電類 ```java public interface AC { int outputAC(); } public class AC220 implements AC { public final int output = 220; @Override public int outputAC() { return output; } } ``` - 介面卡介面,outputDC5V() 方法則用於將輸入的電壓變換為 5V 後輸出 ```java public interface DC5Adapter { int outputDC5V(AC ac); } ``` - 實現電源介面卡 ```java public class PowerAdapter implements DC5Adapter { public static final int voltage = 220; @Override public int outputDC5V(AC ac) { int adapterInput = ac.outputAC(); //變壓器... int adapterOutput = adapterInput / 44; System.out.println("使用PowerAdapter變壓介面卡,輸入AC:" + adapterInput + "V" + ",輸出DC:" + adapterOutput + "V"); return adapterOutput; } } ``` - 使用 ```java public class Test { private List adapters = new LinkedList(); public static void main(String[] args) { AC ac = new AC220(); //例項化220v物件 DC5Adapter adapter = new PowerAdapter(); //例項化介面卡 adapter.outputDC5V(ac); } } ``` - 輸出 ```java 使用PowerAdapter變壓介面卡,輸入AC:220V,輸出DC:5V ``` ### 介面適配 - 在實際開發中,經常會遇到介面中定義了太多的方法,以致於有時我們在一些實現類中並不是都需要 ```java public interface Sourceable { public void method1(); public void method2(); } ``` - 抽象類Wrapper: ```java public abstract class Wrapper implements Sourceable{ public void method1(){} public void method2(){} } ``` - 可選擇的去實現,我們想要的方法 ```java public class SourceSub extends Wrapper { @Override public void method1(){ System.out.println("實現方法一"); } } ``` **參考** - https://juejin.im/post/5ba28986f265da0abc2b6084#heading-7 - https://blog.csdn.net/zhangerqing/article/details/8239539 ## 裝飾模式 **定義** - 裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是作為現有的類的一個包裝。 **實現** - 建立一個介面 ```java public interface Shape { void draw(); } ``` - 建立倆個實現類 ```java public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); } } public class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } } ``` - 建立實現了 *Shape* 介面的抽象裝飾類 ```java public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } @Override public void draw(){ decoratedShape.draw(); System.out.println("Border Color: Red"); } } ``` - 測試 ```java public class Test { public static void main(String[] args) { Shape circle = new Circle(); ShapeDecorator circleShape = new ShapeDecorator(new Circle()); ShapeDecorator rectangleShape = new ShapeDecorator(new Rectangle()); System.out.println("Circle with normal border"); circle.draw(); System.out.println("\nCircle of red border"); circleShape.draw(); System.out.println("\nRectangle of red border"); rectangleShape.draw(); } } ``` - 結果 ```java Circle with normal border Shape: Circle Circle of red border Shape: Circle Border Color: Red Rectangle of red border Shape: Rectangle Border Color: Red ``` **參考** - https://www.runoob.com/design-pattern/decorator-pattern.html ## 代理模式 **定義** - 在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種型別的設計模式屬於結構型模式;在代理模式中,我們建立具有現有物件的物件,以便向外界提供功能介面。 - 其實每個模式名稱就表明了該模式的作用,代理模式就是多一個代理類出來,替原物件進行一些操作,比如我們在租房子的時候回去找中介,為什麼呢?因為你對該地區房屋的資訊掌握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的時候打官司,我們需要請律師,因為律師在法律方面有專長,可以替我們進行操作,表達我們的想法。 **實現** - 建立一個介面 ```java public interface Shape { void draw(); } ``` - 實現 ```java public class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } } ``` - 建立一個代理類 ```java public class ProxyShape implements Shape { private Circle circle; @Override public void draw() { if(circle == null){ circle = new Circle(); } circle.draw(); } } ``` - 測試 ```java public class Test { public static void main(String[] args) { Shape shapeProxy = new ProxyShape(); shape.draw(); } } ``` - 結果 ```java Shape: Circle ``` **參考** - https://blog.csdn.net/zhangerqing/article/details/8239539 ## 外觀模式 **定義** - 外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的介面。這種型別的設計模式屬於結構型模式,它向現有的系統新增一個介面,來隱藏系統的複雜性 - 這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委託呼叫 - 外觀模式就是將他們的關係放在一個Facade類中,降低了類類之間的耦合度 **實現** - 實現類 ```java public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); } } public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); } } public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); } } ``` - Facade類 ```java public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ cpu.startup(); memory.startup(); disk.startup(); } public void shutdown(){ cpu.shutdown(); memory.shutdown(); disk.shutdown(); } } ``` - 測試 ```java public class Test { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); } } ``` - 結果 ```java cpu startup! memory startup! disk startup! cpu shutdown! memory shutdown! disk shutdown! ``` **參考** - https://blog.csdn.net/zhangerqing/article/details/8239539 ## 橋接模式 **定義** - 橋接(Bridge)是用於把抽象化與實現化解耦,使得二者可以獨立變化。這種型別的設計模式屬於結構型模式,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。 - 這種模式涉及到一個作為橋接的介面,使得實體類的功能獨立於介面實現類。這兩種型別的類可被結構化改變而互不影響。 - 橋接模式就是把事物和其具體實現分開,使他們可以各自獨立的變化。橋接的用意是:將抽象化與實現化解耦,使得二者可以獨立變化,像我們常用的JDBC橋DriverManager一樣,JDBC進行連線資料庫的時候,在各個資料庫之間進行切換,基本不需要動太多的程式碼,甚至絲毫不用動,原因就是JDBC提供統一介面,每個資料庫提供各自的實現,用一個叫做資料庫驅動的程式來橋接就行了。 **實現** - 建立橋接實現介面 ```java public interface DrawAPI { public void drawCircle(); } ``` - 建立實現了 DrawAPI 介面的實體橋接實現類 ```java public class RedCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle, color: red); } } public class GreenCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle, color: green); } } ``` - 使用 DrawAPI 介面建立抽象類 Shape。 ```java public abstract class Shape { protected DrawAPI drawAPI; protected Shape(DrawAPI drawAPI){ this.drawAPI = drawAPI; } public abstract void draw(); } ``` - 現了 *Shape* 介面的實體類 ```java public class Circle extends Shape { public Circle(DrawAPI drawAPI) { super(drawAPI); } public void draw() { drawAPI.drawCircle(); } } ``` - 測試 ```java public class BridgePatternDemo { public static void main(String[] args) { Shape redCircle = new Circle(new RedCircle()); Shape greenCircle = new Circle(new GreenCircle()); redCircle.draw(); greenCircle.draw(); } } ``` - 結果 ```java Drawing Circle, color: red Drawing Circle, color: green ``` **參考** - https://blog.csdn.net/zhangerqing/article/details/8239539 - https://www.runoob.com/design-pattern/bridge-pattern.html ## 組合模式 **定義** - 組合模式(Composite Pattern),又叫部分整體模式,是用於把一組相似的物件當作一個單一的物件。組合模式依據樹形結構來組合物件,用來表示部分以及整體層次。這種型別的設計模式屬於結構型模式,它建立了物件組的樹形結構。 - 這種模式建立了一個包含自己物件組的類。該類提供了修改相同物件組的方式。 **實現** - 直接看程式碼 ```java public class Employee { private String name; private String dept; private int salary; private List subordinates; //建構函式 public Employee(String name,String dept, int sal) { this.name = name; this.dept = dept; this.salary = sal; subordinates = new ArrayList(); } public void add(Employee e) { subordinates.add(e); } public void remove(Employee e) { subordinates.remove(e); } public List getSubordinates(){ return subordinates; } public String toString(){ return ("Employee :[ Name : "+ name +", dept : "+ dept + ", salary :" + salary+" ]"); } } ``` - 測試,俄羅斯套娃 ```java public class Test { public static void main(String[] args) { Employee CEO = new Employee("John","CEO", 30000); Employee headSales = new Employee("Robert","Head Sales", 20000); Employee salesExecutive1 = new Employee("Richard","Sales", 10000); CEO.add(headSales); headSales.add(salesExecutive1); //列印該組織的所有員工 System.out.println(CEO); for (Employee headEmployee : CEO.getSubordinates()) { System.out.println(headEmployee); for (Employee employee : headEmployee.getSubordinates()) { System.out.println(employee); } } } } ``` - 結果 ```java Employee :[ Name : John, dept : CEO, salary :30000 ] Employee :[ Name : Robert, dept : Head Sales, salary :20000 ] Employee :[ Name : Richard, dept : Sales, salary :10000 ] ``` **參考** - https://www.runoob.com/design-pattern/composite-pattern.html ## 享元模式 **定義** - 享元模式(Flyweight Pattern)主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式。 - 享元模式嘗試重用現有的同類物件,如果未找到匹配的物件,則建立新物件。 ### 典型享元模式 - 典型享元類(享元:Flyweight,w不是大寫) ```java class Flyweight { //內部狀態innerState作為成員變數,同一個享元物件其內部狀態是一致的 private String innerState; public Flyweight(String innerState) { this.innerState = innerState; } //外部狀態outerState在使用時由外部設定,不儲存在享元物件中,即使是同一個物件 public void operation(String outerState) { //...... } } ``` - 典型享元工廠類 ```java class FlyweightFactory { //定義一個HashMap用於儲存享元物件,實現享元池 private HashMap flyweights = newHashMap(); public Flyweight getFlyweight(String key){ //如果物件存在,則直接從享元池獲取 if(flyWeights.containsKey(key)){ return (Flyweight) flyweights.get(key); } else { //如果物件不存在,先建立一個新的物件新增到享元池中,然後返回 Flyweight fw = newConcreteFlyweight(); flyweights.put(key,fw); return fw; } } } ``` ### 通用寫法 - 解決某些場景頻繁生成例項問題;使用泛型,節省寫判斷邏輯 ```java /** * 經典享元模式方法 * 場景:解決某些場景頻繁生成例項問題;使用泛型,節省寫判斷邏輯 * 使用:String s = classicFlyweight(String.class); */ private Map flyweightMap; private T classicFlyweight(Class clazz) { T t; if (flyweightMap == null) flyweightMap = new HashMap<>(); String key = clazz.getName(); if (flyweightMap.get(key) != null) { t = (T) flyweightMap.get(key); }else { try { t = clazz.newInstance(); flyweightMap.put(key, t); } catch (Exception e) { t = null; } } return t; } ``` ### 連線池的實現 - 資料庫連線池 - 通過連線池的管理,實現了資料庫連線的共享,不需要每一次都重新建立連線,節省了資料庫重新建立的開銷,提升了系統的效能! ```java public class ConnectionPool { private Vector pool; //公有屬性 private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; private static ConnectionPool instance = null; Connection conn = null; //構造方法,做一些初始化工作 private ConnectionPool() { pool = new Vector(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } //返回連線到連線池 public synchronized void release() { pool.add(conn); } //返回連線池中的一個數據庫連線 public synchronized Connection getConnection() { if (pool.size() >
0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; } } } ``` **參考** - https://juejin.im/post/5ba9ff4b6fb9a05d0f16df6a - https://blog.csdn.net/zhangerqing/article/details/8239539 # 行為型模式 ## 思考 ### 策略模式和狀態模式 **相同點** - 兩者通過將行為和狀態拆分成一系列小的元件,由條件和狀態進行功能更替,這樣符合開閉原則,便於擴充套件。此外均可作為if else或者分支的替換方案;支援的最大行為和狀態均有限; **不同點** - 策略模式中,類的功能是根據當前條件主動更改; - 狀態模式中,類的功能是被動由當前狀態更改; - 策略模式中每個行為或演算法之間沒有關聯; - 狀態模式中的狀態之間有關聯,並且狀態本身控制著狀態轉移; ## 直譯器模式 **定義** - 直譯器模式(Interpreter Pattern)提供了評估語言的語法或表示式的方式,它屬於行為型模式。這種模式實現了一個表示式介面,該介面解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。 **實現** - 建立一個表示式介面。 ```java public interface Expression { public boolean interpret(String context); } ``` - 建立實現了上述介面的實體類。 ```java public class TerminalExpression implements Expression { private String data; public TerminalExpression(String data){ this.data = data; } @Override public boolean interpret(String context) { if(context.contains(data)){ return true; } return false; } } public class OrExpression implements Expression { private Expression expr1 = null; private Expression expr2 = null; public OrExpression(Expression expr1, Expression expr2) { this.expr1 = expr1; this.expr2 = expr2; } @Override public boolean interpret(String context) { return expr1.interpret(context) || expr2.interpret(context); } } public class AndExpression implements Expression { private Expression expr1 = null; private Expression expr2 = null; public AndExpression(Expression expr1, Expression expr2) { this.expr1 = expr1; this.expr2 = expr2; } @Override public boolean interpret(String context) { return expr1.interpret(context) && expr2.interpret(context); } } ``` - Test 使用 Expression 類來建立規則,並解析它們。 ```java public class Test { //規則:Robert 和 John 是男性 public static Expression getMaleExpression(){ Expression robert = new TerminalExpression("Robert"); Expression john = new TerminalExpression("John"); return new OrExpression(robert, john); } //規則:Julie 是一個已婚的女性 public static Expression getMarriedWomanExpression(){ Expression julie = new TerminalExpression("Julie"); Expression married = new TerminalExpression("Married"); return new AndExpression(julie, married); } public static void main(String[] args) { Expression isMale = getMaleExpression(); Expression isMarriedWoman = getMarriedWomanExpression(); System.out.println("John is male? " + isMale.interpret("John")); System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie")); } } ``` - 結果 ```java John is male? true Julie is a married women? true ``` **參考** - https://www.runoob.com/design-pattern/interpreter-pattern.html ## 狀態模式 **定義** - 在狀態模式(State Pattern)中,類的行為是基於它的狀態改變的;我們建立表示各種狀態的物件和一個行為隨著狀態物件改變而改變的 context 物件。 **實現** - 建立一個介面。 ```java public interface State { public void doAction(Context context); } ``` - 倆個狀態實現類. ```java public class StartState implements State { public void doAction(Context context) { System.out.println("Player is in start state"); context.setState(this); } public String toString(){ return "Start State"; } } public class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } public String toString(){ return "Stop State"; } } ``` - 建立 Context 類。 ```java public class Context { private State state; public Context(){ state = null; } public void setState(State state){ this.state = state; } public State getState(){ return state; } } ``` - 測試 ```java public class StatePatternDemo { public static void main(String[] args) { Context context = new Context(); StartState startState = new StartState(); startState.doAction(context); System.out.println(context.getState().toString()); StopState stopState = new StopState(); stopState.doAction(context); System.out.println(context.getState().toString()); } } ``` - 結果 ```java Player is in start state Start State Player is in stop state Stop State ``` **參考** - https://www.runoob.com/design-pattern/state-pattern.html ## 策略模式 **定義** - 在策略模式(Strategy Pattern)中,一個類的行為或其演算法可以在執行時更改。這種型別的設計模式屬於行為型模式。 - 在策略模式中,我們建立表示各種策略的物件和一個行為隨著策略物件改變而改變的 context 物件。策略物件改變 context 物件的執行演算法。 - 策略模式定義了一系列演算法,並將每個演算法封裝起來,使他們可以相互替換,且演算法的變化不會影響到使用演算法的客戶. **實現** - 策略介面,定義策略執行介面 ```java public interface Strategy { int calculate(int a, int b); } ``` - 具體策略類,實現策略介面,提供具體演算法 ```java // 加法演算法 public class AddStrategy implements Strategy{ @Override public int calculate(int a, int b) { return a + b; } } // 減法演算法 public class SubtractStrategy implements Strategy{ @Override public int calculate(int a, int b) { return a - b; } } ``` - Context類,持有具體策略類的例項,負責呼叫具體演算法 ```java public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } // 動態替換演算法(策略) public void replaceStrategy(Strategy strategy) { this.strategy = strategy; } public int calculate(int a, int b) { return strategy.calculate(a, b); } } ``` - 測試 ```java public class Test{ public static void main(String[] args) { Strategy addStrategy = new AddStrategy(); Context context = new Context(addStrategy); // 輸出3 System.out.println(context.calculate(1, 2)); Strategy subStrategy = new SubtractStrategy(); // 動態替換演算法(策略) context.replaceStrategy(subStrategy); // 輸出-1 System.out.println(context.calculate(1, 2)); } } ``` - 測試main方法中,我們先使用"加法策略(演算法)"建立一個context,然後呼叫calculate(1,2)方法得到結果3。然後動態替換策略為"減法策略(演算法)",再次呼叫calculate(1,2)得到結果-1。 **參考** - https://www.jianshu.com/p/d0c1b312d563 ## 觀察者模式 **定義** - 當物件間存在一對多關係時,則使用觀察者模式(Observer Pattern)。比如,當一個物件被修改時,則會自動通知依賴它的物件。 **實現** - 抽象觀察者介面(Observer) - 為所有的具體觀察者定義一個介面,在得到主題通知時更新自己。 ```java public interface Observer { //更新內容 public void update(String msg); } ``` - 抽象主題介面(Subject) - 它把所有觀察者物件的引用儲存到一個聚集裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,以及通知所有觀察者。 ```java public interface Subject { //新增觀察者 void attach(Observer observer); //刪除觀察者 void detach(Observer observer); //通知更新 void notify(String msg); } ``` - 觀察者介面實現類 - 實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題狀態保持一致。 ```java public class TestObserver implements Observer{ private String info; public TestObserver(String info){ this.info = info; } @Override public void update(String msg) { System.out.println(info + "----" + msg); } } ``` - 主題介面實現類 - 將有關狀態存入具體觀察者物件;當具體主題內部狀態放生改變時,通知所有註冊過的觀察者。 ```java public class TestSubject implements Subject{ private List mList = new ArrayList(); @Override public void attach(Observer observer) { mList.add(observer); } @Override public void detach(Observer observer) { mList.remove(observer); } @Override public void notify(String msg) { for (Observer observer : mList) { observer.update(msg); } } } ``` - 測試 ```java public class TestMain { public static void main(String[] args) { Subject subject = new TestSubject(); Observer observerA = new TestObserver("A:"); Observer observerB = new TestObserver("B:"); subject.attach(observerA); subject.attach(observerB); subject.notify("通知One"); subject.detach(observerA); subject.notify("通知Two"); } } ``` - 日誌列印 ![image.png](https://cdn.jsdelivr.net/gh/CNAD666/MyData/pic/thought/20201123123446.png) ## 中介者模式 **定義** - 中介者模式(Mediator Pattern)是用來降低多個物件和類之間的通訊複雜性。這種模式提供了一箇中介類,該類通常處理不同類之間的通訊,並支援鬆耦合,使程式碼易於維護。 **實現** - 建立中介類 ```java public class ChatRoom { public static void showMessage(User user, String message){ System.out.println(new Date().toString() + " [" + user.getName() +"] : " + message); } } ``` - 建立 user 類。 ```java public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public User(String name){ this.name = name; } public void sendMessage(String message){ ChatRoom.showMessage(this,message); } } ``` - 測試 ```java public class Test { public static void main(String[] args) { User robert = new User("Robert"); User john = new User("John"); robert.sendMessage("Hi! John!"); john.sendMessage("Hello! Robert!"); } } ``` - 結果 ```java Thu Jan 31 16:05:46 IST 2013 [Robert] : Hi! John! Thu Jan 31 16:05:46 IST 2013 [John] : Hello! Robert! ``` **參考** - https://www.runoob.com/design-pattern/mediator-pattern.html ## 備忘錄模式 **定義** - 備忘錄模式(Memento Pattern)儲存一個物件的某個狀態,以便在適當的時候恢復物件。 **實現** - Original類是原始類,裡面有需要儲存的屬性value及建立一個備忘錄類,用來儲存value值。Memento類是備忘錄類,Storage類是儲存備忘錄的類,持有Memento類的例項 ```java public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } } public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } ``` - 測試 ```java public class Test { public static void main(String[] args) { // 建立原始類 Original origi = new Original("egg"); // 建立備忘錄 Storage storage = new Storage(origi.createMemento()); // 修改原始類的狀態 System.out.println("初始化狀態為:" + origi.getValue()); origi.setValue("niu"); System.out.println("修改後的狀態為:" + origi.getValue()); // 回覆原始類的狀態 origi.restoreMemento(storage.getMemento()); System.out.println("恢復後的狀態為:" + origi.getValue()); } } ``` - 結果 ```java 初始化狀態為:egg 修改後的狀態為:niu 恢復後的狀態為:egg ``` **參考** - https://blog.csdn.net/zhangerqing/article/details/8245537 ## 命令模式 **定義** - 命令模式(Command Pattern)是一種資料驅動的設計模式。請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令。 **實現** - 建立一個命令介面。 ```java public interface Order { void execute(); } ``` - 建立一個請求類。 ```java public class Stock { private String name = "ABC"; private int quantity = 10; public void buy(){ System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought"); } } ``` - 建立實現了 Order 介面的實體類。 ```java public class BuyStock implements Order { private Stock abcStock; public BuyStock(Stock abcStock){ this.abcStock = abcStock; } public void execute() { abcStock.buy(); } } ``` - 建立命令呼叫類。 ```java public class Broker { private List orderList = new ArrayList(); public void takeOrder(Order order){ orderList.add(order); } public void placeOrders(){ for (Order order : orderList) { order.execute(); } orderList.clear(); } } ``` - 測試 ```java public class CommandPatternDemo { public static void main(String[] args) { Stock abcStock = new Stock(); BuyStock buyStockOrder = new BuyStock(abcStock); Broker broker = new Broker(); broker.takeOrder(buyStockOrder); broker.placeOrders(); } } ``` - 結果 ```java Stock [ Name: ABC, Quantity: 10 ] bought ``` **參考** - https://www.runoob.com/design-pattern/command-pattern.html ## 責任鏈模式 **定義** - 責任鏈模式(Chain of Responsibility Pattern)為請求建立了一個接收者物件的鏈。這種模式給予請求的型別,對請求的傳送者和接收者進行解耦。 **實現** - 建立抽象的記錄器類 ```java public abstract class AbstractLogger { public static int INFO = 1; public static int DEBUG = 2; public static int ERROR = 3; protected int level; //責任鏈中的下一個元素 protected AbstractLogger nextLogger; public void setNextLogger(AbstractLogger nextLogger){ this.nextLogger = nextLogger; } public void logMessage(int level, String message){ if(this.level <= level){ write(message); } if(nextLogger != null){ nextLogger.logMessage(level, message); } } abstract protected void write(String message); } ``` - 建立擴充套件了該記錄器類的實體類 ```java public class ConsoleLogger extends AbstractLogger { public ConsoleLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("Standard Console::Logger: " + message); } } public class ErrorLogger extends AbstractLogger { public ErrorLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("Error Console::Logger: " + message); } } public class FileLogger extends AbstractLogger { public FileLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("File::Logger: " + message); } } ``` - 測試 ```java public class Test { private static AbstractLogger getChainOfLoggers(){ AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR); AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG); AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO); errorLogger.setNextLogger(fileLogger); fileLogger.setNextLogger(consoleLogger); return errorLogger; } public static void main(String[] args) { AbstractLogger loggerChain = getChainOfLoggers(); loggerChain.logMessage(AbstractLogger.INFO, "This is an information."); loggerChain.logMessage(AbstractLogger.DEBUG, "This is a debug level information."); loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information."); } } ``` - 結果 ```java Standard Console::Logger: This is an information. File::Logger: This is a debug level information. Standard Console::Logger: This is a debug level information. Error Console::Logger: This is an error information. File::Logger: This is an error information. Standard Console::Logger: This is an error information. ``` **參考** - https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html ## 訪問者模式 **定義** - 在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執行演算法。通過這種方式,元素的執行演算法可以隨著訪問者改變而改變。這種型別的設計模式屬於行為型模式。根據模式,元素物件已接受訪問者物件,這樣訪問者物件就可以處理元素物件上的操作。 **實現** - Visitor類,存放要訪問的物件 ```java public interface Visitor { public void visit(Subject sub); } public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); } } ``` - Subject類,accept方法,接受將要訪問它的物件,getSubject()獲取將要被訪問的屬性 ```java public interface Visitor { public void visit(Subject sub); } public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); } } ``` - Subject類,accept方法,接受將要訪問它的物件,getSubject()獲取將要被訪問的屬性 ```java public interface Subject { public void accept(Visitor visitor); public String getSubject(); } public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; } } ``` - 測試 ```java public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); } } ``` - 結果:visit the subject:love **參考** - https://blog.csdn.net/zhangerqing/article/details/8245537 ## 迭代器模式 **定義** - 迭代器模式(Iterator Pattern)是 Java 和 .Net 程式設計環境中非常常用的設計模式。這種模式用於順序訪問集合物件的元素,不需要知道集合物件的底層表示。 **實現** - 建立介面 ```java public interface Iterator { public boolean hasNext(); public Object next(); } public interface Container { public Iterator getIterator(); } ``` - 建立實現了 Container 介面的實體類。該類有實現了 Iterator 介面的內部類 NameIterator。 ```java public class NameRepository implements Container { public String names[] = {"Robert" , "John" ,"Julie" , "Lora"}; @Override public Iterator getIterator() { return new NameIterator(); } private class NameIterator implements Iterator { int index; @Override public boolean hasNext() { if(index < names.length){ return true; } return false; } @Override public Object next() { if(this.hasNext()){ return names[index++]; } return null; } } } ``` - 測試 ```java public class Test { public static void main(String[] args) { NameRepository namesRepository = new NameRepository(); for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){ String name = (String)iter.next(); System.out.println("Name : " + name); } } } ``` - 結果 ```java Name : Robert Name : John Name : Julie Name : Lora ``` **參考** - https://www.runoob.com/design-pattern/iterator-pattern.html ## 模板模式 **定義** - 在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。 **實現** - 建立一個抽象類 ```java public abstract class Game { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); //模板 public final void play(){ //初始化遊戲 initialize(); //開始遊戲 startPlay(); //結束遊戲 endPlay(); } } ``` - 實現類 ```java public class Football extends Game { @Override void endPlay() { System.out.println("Football Game Finished!"); } @Override void initialize() { System.out.println("Football Game Initialized! Start playing."); } @Override void startPlay() { System.out.println("Football Game Started. Enjoy the game!"); } } ``` - 測試 ```java public class Test { public static void main(String[] args) { Game game = new Football(); game.play(); } } ``` - 結果 ```java Football Game Initialized! Start playing. Football Game Started. Enjoy the game! Football Game Finished! ``` **參考** - https://www.runoob.com/design-pattern/template-pattern.html # 最後 - 文章原始碼:[一文總結設計模式](https://github.com/CNAD666/study-notes/blob/master/%E5%8D%9A%E5%AE%A2/%E6%80%9D%E6%83%B3/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F) - 提供良好的搬運格式,搬運使用時,請附上引用出處