震驚!小豬的設計模式初涉總結!純乾貨~

震驚!小豬的設計模式初涉總結!純乾貨~

標籤: 知識點總結


描述性文字

      今年一月初有了離職的念頭後,就盤算着把設計模式給過一遍,索性就開了一個新的系列:
《如何讓孩子愛上設計模式》。在編寫過程中經歷了舊項目重構,離職交接,寫簡歷,投簡歷,
面試,搬家等階段,零零星星,總算是堅持寫完,自己也是收穫頗多,比如面試的時候別人問我用
過,瞭解什麼設計模式,不用絞盡腦汁的憋出那麼幾個爛大街的,單例,建造者,簡單工廠模式…
當然只是對各種設計模式有了一個初步的瞭解,靈活運用還得在實踐和閱讀優秀源碼中去慢慢體會!
和《Git大法好》系列一樣,《如何讓孩子愛上設計模式》初衷是想讓初學者更好更快的去理解每種
模式,依舊本着通熟易懂的理念去編寫,同時也意味着有很多廢話,所以總結提取裏面的要點精華,
相關套路顯得非常重要,遂有此文。描述文字大概就這些,本文會一直更新,如果文中有描述錯誤
或寫得不好的地方歡迎指出,萬分感激~

本節各種設計模式示例代碼下載:https://github.com/coder-pig/DesignPatternsExample


1.面向對象的三大特性

1)封裝(Encapsulation)

隱藏對象的具體實現細節,通過共有方法暴露對象的功能。
內部結構可以自由修改,同時可對成員進行更加精確的控制
(比如在setter方法中加值合法判斷)

2)繼承(Inheritance)

使用已經存在的類作爲基礎類(父類),在此基礎上建立新類(子類),
子類既可複用父類的功能,也能進行擴展,從而實現代碼複用。
另外,Java不能像C++那樣同時繼承多個父類,只能樹形的繼承
比如:Man -> Human -> Animal,或通過接口和內部類實現多繼承。

另外,關於繼承還需注意以下幾點:

  • 1.子類擁有父類非private的屬性與方法
  • 2.構造方法只能調用,不能實現,子類默認調用父類的無參構造方法,
    如果父類沒有無參的構造方法,需要使用super顯式調用!
  • 3.慎用繼承,要考慮是否需要從子類向父類進行向上轉型!

3)多態(Polymorphism)

定義:一個類實例的相同方法在不同的情形下有不同的表現形式

分爲以下兩種:

  • 編譯時多態(OverLoading)—— 方法重載
  • 運行時多態(OverRidding)—— 繼承 + 方法重寫 + 向上轉型(父類引用指向子類對象)

運行時多態(動態綁定,new後面什麼類型,動態類型就是什麼類型)

示例如下:

class Animal() { fun show() { println("動物") }}
class PeopleAnimal() { fun show() { println("人類") }}
//下述代碼打印結果:人類
Animal animal = new People()
animal.show()

2.類與類間的關係

口訣雞溼衣冠劇組(繼承,實現,依賴,關聯,聚合,組合)

繼承和實現就不說了,後面四個只是 語意層次 的區別
兩個類的相關程度,依賴 < 關聯 < 聚合 < 組合

依次的UML類圖標記:

  • 繼承/泛化(Generalization): 子類 父類

  • 實現(Realization):實現類 接口

  • 依賴(Dependency):不持有引用,具體表現:局部變量,函數參數,
    返回值使用 依賴類,比如大佬依賴於遞茶小弟;

  • 關聯(Association):持有引用,具體表現:成員變量,
    箭頭指向被關聯類,可雙向,一對多或多對多:

  • 聚合(Aggregation):成員變量,關聯是處於同一層次的,而聚合
    整體和局部層次的,比如:社團小弟,另外即使沒有了社團,
    小弟們依舊可以到別的地方搞事情。

  • 組合(Composition):與聚合類似,程度更加強烈,共生死,組合類
    負責被組合類的生命週期,比如: 社團大佬,如果沒了社團,
    大佬也就就不能存在了。


3.面向對象七大原則

  • 單一職責原則(Single Responsibility Principle)
    每一個類應該專注於做一件事情。 即:高內聚,低耦合。

  • 開閉原則(Open Close Principle)
    一個對象對擴展開放對修改關閉。即:對類的改動
    是通過增加代碼進行的,而不是修改現有代碼。

  • 里氏替換原則(Liskov Substitution Principle)
    在任何父類出現的地方都可以用它的子類來替代。

  • 依賴倒置原則(Dependence Inversion Principle)
    要依賴於抽象不要依賴於具體實現

  • 接口隔離原則(Interface Segregation Principle)
    應當爲客戶端提供儘可能小的單獨的接口,而不是提供大的總的接口。

  • 迪米特原則(Law Of Demeter)
    一個對象應當儘量少地與其他對象之間發生相互作用,使得系統功能模塊相對獨立。

  • 組合/聚合複用原則(Composite/Aggregate Reuse Principle)
    儘量使用組合/聚合的方式,而不是使用繼承


23種設計模式

  • 創建型(5種):主要用於處理對象的創建,實例化對象:
    單例建造者原型工廠方法抽象工廠

  • 結構型(7種):處理類或對象間的組合
    適配器裝飾者結合橋接外觀享元代理

  • 行爲型(11種):描述類或對象怎樣進行交互和職責分配
    策略觀察者迭代器命令備忘錄中介者解釋器訪問者責任鏈狀態模板方法


一. 單例模式(Singleton Pattern)

作用:保證 類在內存中對象唯一性

適用場景

  • 1.避免創建多個實例浪費資源
  • 2.避免多個實例因多次調用而出現錯誤
  • 3.一般寫工具類,線程池,緩存,數據庫會用到。

套路(三個要點):

  • 1.不允許在類外new對象 —— 構造方法私有化
  • 2.在類中創建對象 —— 通過new在本類中創建一個實例
  • 3.對外提供獲取該實例的方法 —— 定義公有方法返回創建的實例

餓漢與懶漢的區別

前者在類裝載時就實例化,後者只有在第一次被使用時才實例化。
(餓漢的優點是避免線程同步問題,缺點是即使沒用到這個實例還是會加載)
(懶漢的優點是實現了懶加載,但需要解決線程安全問題!)

7種單例套路

1)餓漢式,沒有實現懶加載~

public class Singleton() {
    private static Singleton instance = new Singleton();
    private Singleton(){ }
    public static Singleton getInstance() { 
        return instance;  
    }
}
//獲取單例對象
Singleton mSingleton = Singleton.getInstance();

2)懶漢式

雖然達到了懶加載,但是卻存在線程安全問題,比如有兩個線程都
剛好執行完if(instance == null),接着準備執行instance = new Singleton()
語句,這樣的結果會導致我們實例化了兩個Singleton對象
爲了解決線程不安全問題,可以對getInstance()方法加鎖。

public class Singleton {
    private static Singleton instance = null;
    private Singleton() { }
    private static Singleton getInstance() {
        if(instance == null) { 
            instance = new Singleton(); 
        }
        return instance;
    }
}

3)懶漢式加鎖版

爲getInstance方法加鎖雖然保證了線程安全,但是每次執行getInstance()
都需要同步,而實例化對象只需要執行一次就夠了,後面獲取該示例,
應該直接return就好了,方法同步效率太低,一種改進後的寫法是:
synchronized (Singleton.class) { instance = new Singleton(); }
但是,這樣寫依然是線程不安全的,如果你還是想用懶漢式的話,推薦
雙重檢查鎖定(DCL,Double Check Lock)。

public class Singleton {
    private static Singleton instance = null;
    private Singleton() { }
    private static synchronized Singleton getInstance() {
        if(instance == null) { 
            instance = new Singleton(); 
        }
        return instance;
    }
}

4)懶漢式雙重校驗鎖(DCL)

代碼中進行了兩次if檢查,這樣就可以保證線程安全,初始化一次後,
後面再次訪問時,if檢查,直接return 實例化對象。volatile是1.5後
引入的,volatile關鍵字會屏蔽Java虛擬機所做的一些代碼優化,會導
致系統運行效率降低,而更好的寫法是使用靜態內部類來實現單例!

public class Singleton{
    private static volatile Singleton instance = null;
    private Singleton() { }
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) instance = new Singleton();
            }
        }
        return instance;
    }
 }

5)靜態內部類實現單例(推薦)

和餓漢式類似,都是通過類加載機制來保證初始化實例的
時候只有一個線程,從而避免線程安全問題,餓漢式的
Singleton類被加載時,就會實例化,而靜態內部類這種,
當Singleton類被加載時,不會立即實例化,調用getInstance()
方法纔會裝載SingletonHolder類,從而完成Singleton的實例化。

public class Singleton {
    private Singleton() { }
    private static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton()
    }
}

6)枚舉實現單例

INSTANCE即爲SingletonEnum類型的引用,得到它就可以調用
枚舉中的方法。既避免了線程安全問題,還能防止反序列化
重新創建新的對象,但是失去了類的一些特性,而且沒有延時加載。

public enum SingletonEnum {
    INSTANCE;
    private Singleton instance;
    SingletonEnum() { 
        instance = new Singleton();
    }
    public Singleton getInstance() { 
        return instance; 
    }
}
//調用方式
SingletonEnum.INSTANCE.method();

7)容器實現單例

將多種單例類型注入到一個統一的管理類中,在使用時根據key獲取對象
對應類型的對象。這種方式使得我們可以管理多種類型的單例,並且在使
用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用
戶隱藏了具體實現,降低了耦合度。

public class SingletonManager {
    private static Map<String,Object> objMap = new HashMap<String,Object>();
    private Singleton(){ }
    public static void registerService(String key,Object instance) {
        if(!objMap.containsKey(key)) {
            objMap.put(key,instance);
        }
    }
    public static Object getService(String key) {
        return objMap.get(key);
    }
}

二. 建造者模式(Builder Pattern)

複雜對象的構建與表示分離 開來,使得同樣的構建過程可以
創建不同的表示,缺點是可能產生多餘的創建者與構建過程對象
消耗內存,不適用於內部建造順序不穩定,變化複雜的對象
可能導致需要創建很多具體的建造者來實現這些變化。
例子:玩遊戲創建角色時的自定義,不同的搭配生成不同的角色。

四個角色與UML類圖

示例代碼

//產品類
class Character {
    private String sex;
    private String face;
    private String clothes;

    void setSex(String sex) { this.sex = sex;}
    void setFace(String face) { this.face = face; }
    void setClothes(String clothes) { this.clothes = clothes;}

    String showMsg() { return "你創建了一個穿着 " + clothes + " 一副 " + face + " 的" + sex + "ヾ(≧▽≦*)o 戳菊狂笑~"; }
}

//抽象Builder接口
interface Builder {
    void setSex(String sex);
    void setFace(String face);
    void setClothes(String clothes);
    Character build();
}

//Builder接口實現類
class ConcreteBuilder implements Builder {
    private Character mCharacter = new Character();

    @Override public void setSex(String sex) { mCharacter.setSex(sex); }
    @Override public void setFace(String face) { mCharacter.setFace(face); }
    @Override public void setClothes(String clothes) { mCharacter.setClothes(clothes); }
    @Override public Character build() {return mCharacter;}
}

//裝配過程類
class Director {
    private Builder mBuilder = null;
    Director(Builder builder) { this.mBuilder = builder; }
    Character createCharacter(String sex, String face, String clothes) {
        this.mBuilder.setSex(sex);
        this.mBuilder.setFace(face);
        this.mBuilder.setClothes(clothes);
        return mBuilder.build();
    }
}

//客戶端調用類
public class Game {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Character character =  director.createCharacter("基佬","硬漢臉","死庫水");
        System.out.println(character.showMsg());
    }
}

輸出結果


三. 原型模式(Prototype Pattern)

下面兩種場景可以考慮使用原型模式:

  • 1.當初始化類對象需要消耗非常多資源,或者說要進行繁瑣
    的數據準備或者權限,如果想簡化創建,可以使用原型模式。
  • 2.一個對象提供給其他對象訪問,而各個調用者可能都需要
    修改對象的值,可以考慮使用原型模式克隆多個對象供調用者
    使用(保護性拷貝

三個角色與UML類圖

Java中 == 與equals的區別

  • ==基本數據類型(int,long等),比較存儲的值是否相等
    對比的是引用類型,比較的是所指對象地址是否相等

  • equals,不能用於比較基本數據類型,如果**沒對equals()方法進行
    重寫,比較的是指向的對象地址,如果想要比較對象內容,需要自行重寫**
    方法,做相應的判斷!!!!String調equals是可以判斷內容是否一樣,是
    因爲對equals()方法進行了重寫,具體可參見源碼!

克隆需要滿足的三個條件

  • 1.x.clone()!=x ,即 不是同一對象
  • 2.x.clone().getClass == x.getClass(),即 對象類型一致
  • 3.如果對象obj的equals()方法定義恰當的話,那麼
    obj.clone().equals(obj) == true應當是成立的。(推薦,不強制)

Java中如何使用

Prototype原型類(想被克隆的類)實現Cloneable接口重寫clone()方法。

ConcretePrototype cp1 = new ConcretePrototype(); 
ConcretePrototype cp2 = (ConcretePrototype)cp1.clone();

需注意

  • 1.執行克隆方法,不會調用構造方法
  • 2.克隆會生成的新的對象成員,但指向的卻是同一個內存地址
  • 3.克隆前後數據類型一致
  • 4.克隆的時候,類中基本數據類型的屬性會新建,但是引用類型
    只會生成個新的引用變量,引用變量的地址依舊指向同一個內存地址!

實現深拷貝的兩種套路

這種只新建基本類型數據,不新建引用類型數據,稱爲淺拷貝
如果連引用類型數據也新建的話,則稱爲深拷貝

兩個套路:

1.引用類型也實現Cloneable接口,如果屬性的類型也是對象,
那麼需要一直遞歸的克隆下去
2.序列化,屬性的類型是引用類型的話,需要實現Serializable接口
然後自己寫個方法來在裏面**完成對象轉二進制流與二進制流轉
對象**的方法,然後返回克隆後的對象!

具體代碼見:3.Prototype Pattern


四.工廠方法模式(Factory Method Pattern)

關於三種工廠模式,其實理解起來非常簡單,只是把對象的創建放到一個
特定的類中,相比起我們直接new對象,這種套路會寫多幾個類,但是
卻擁有更好的擴展性,而且當創建的對象發生改變,可以減少一定的修改量。
(想想你在項目中有一個類在多處都new了,現在需要對這個類的構造
方法,或者相關參數做些修改,你需要找到每個new這個類的地方進行
修改,而如果你把工作都丟給一個工廠類,你可能只需要修改這個類)
另外,簡介下這幾種工廠模式的區別:

  • 簡單工廠模式:最簡單的直接把new對象丟到一個工廠類中;
  • 工廠方法模式:對工廠類進行抽象,實現具體工廠類以創建不同對象;
  • 抽象工廠模式:當工廠需要創建多種相互關聯或依賴的對象,有兩個名
    產品等級結構產品族,具體是什麼的自己看~

簡單工廠模式的三個角色

代碼示例

abstract class Tea {
    public abstract void 加奶茶();
    public abstract void 加料();
}

class YeGuoTea extends Tea{
    @Override public void 加奶茶() { System.out.println("加了一把奶茶");}
    @Override public void 加料() {System.out.println("加了一把椰果");}
}

class ZhenZhuTea  extends Tea{
    @Override public void 加奶茶() { System.out.println("加了一把奶茶");}
    @Override public void 加料() {System.out.println("加了一把珍珠");}
}

public class Me {
    public static Tea makeTea(int type) {
        System.out.println("==============");
        Tea tea = type == 0 ? new ZhenZhuTea() : new YeGuoTea();
        tea.加奶茶();
        tea.加料();
        return tea;
    }
}

public class Store {
    public static void main(String[] args) {
        for (int i = 0;i < 3;i++) {
            Tea tea = Me.makeTea(buyTea()); //小豬製作奶茶
        }
    }

    /* 模擬用戶下單,0代表要珍珠奶茶,1代表要椰果奶茶 */
    private static int buyTea() {
        return new Random().nextInt(2);
    }
}

輸出結果


工廠方法模式(靜態工廠)

其實就是在簡單工廠模式基礎上,把工廠創建不同產品的內部邏輯抽取出來,
生成一個抽象工廠,再創建具體工廠類,生產不同的產品。

UML類圖

代碼示例

//工廠接口/抽象類
abstract class MakeTea {
    abstract Tea 小豬帶特效的奶茶製作工藝();
}

//工廠實現類1
class ZhenZhuMakeTea extends MakeTea {
    @Override
    Tea 小豬帶特效的奶茶製作工藝() {
        System.out.println("====== 珍珠小弟炮製港式珍珠奶茶 ======");
        Tea tea = new ZhenZhuTea();
        tea.加奶();
        tea.加茶();
        tea.加料();
        tea.打包();
        return tea;
    }
}

//工廠實現類2
class YeGuoMakeTea extends MakeTea {
    @Override
    Tea 小豬帶特效的奶茶製作工藝() {
        System.out.println("====== 椰果小弟炮製日式椰果奶茶 ======");
        Tea tea = new YeGuoTea();
        tea.加奶();
        tea.加茶();
        tea.加料();
        tea.打包();
        return tea;
    }
}

//客戶端調用
public class StoreS {
    public static void main(String[] args) {
        //初始化兩個小弟
        ZhenZhuMakeTea zhenzhu = new ZhenZhuMakeTea();
        YeGuoMakeTea yeguo = new YeGuoMakeTea();

        for (int i = 0;i < 3;i++) {
            Tea tea = buyTea() == 0 ? zhenzhu.小豬帶特效的奶茶製作工藝() 
                : yeguo.小豬帶特效的奶茶製作工藝();
        }
    }

    /* 模擬用戶下單,0代表要珍珠奶茶,1代表要椰果奶茶 */
    private static int buyTea() {
        return new Random().nextInt(2);
    }
}

輸出結果

另外還可以通過反射簡潔生產過程,直接傳入產品的類類型
生成對應的產品,示例如下:

abstract class SMakeTea {
    public abstract <T extends Tea> T 小豬帶特效的奶茶製作工藝(Class<T> clz);
}

class SMe extends SMakeTea {
    @Override
    public <T extends Tea> T 小豬帶特效的奶茶製作工藝(Class<T> clz) {
        System.out.println("==============");
        Tea tea = null;
        try {
            tea = (Tea) Class.forName(clz.getName()).newInstance();
            tea.加奶();
            tea.加茶();
            tea.加料();
            tea.打包();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) tea;
    }
}

public class SStore {
    public static void main(String[] args) {
        SMe me = new SMe();
        me.小豬帶特效的奶茶製作工藝(ZhenZhuTea.class);
        me.小豬帶特效的奶茶製作工藝(YeGuoTea.class);
    }
}

六. 抽象工廠模式(Abstract Factory Pattern)

代碼示例

//抽象產品類1
abstract class Drink {
    public abstract void drink();
}

//抽象產品類2
abstract class Snack {
    public abstract void snack();
}

//具體產品類們
class MilkTea extends Drink {
    @Override public void drink() { System.out.println("一杯奶茶"); }
}

class Juice extends Drink {
    @Override public void drink() { System.out.println("一杯果汁"); }
}

class HandGrab extends Snack {
    @Override public void snack() { System.out.println("一個手抓餅"); }
}

class FishBall extends Snack {
    @Override public void snack() { System.out.println("一碗魚蛋"); }
}

//抽象工廠類
abstract class MakeFood {
    abstract Drink createMakeDrink();
    abstract Snack createMakeSnack();
}

//具體工廠類1
class FirstXiaoDi extends MakeFood {
    @Override public Drink createMakeDrink() { return new MilkTea(); }
    @Override public Snack createMakeSnack() { return new HandGrab(); }
}

//具體工廠類2
class SecondXiaoDi extends MakeFood {
    @Override public Drink createMakeDrink() { return new Juice(); }
    @Override public Snack createMakeSnack() { return new FishBall(); }
}

//客戶端調用
public class Store {
    public static void main(String[] args) {
        //初始化兩個小弟
        MakeFood xiaodi1 = new FirstXiaoDi();
        MakeFood xiaodi2 = new SecondXiaoDi();

        for(int i = 0;i < 4 ;i++) {
            System.out.println("====== 根據訂單配餐: ======");
            Drink drink = buyDrink() == 0 ?
                xiaodi1.createMakeDrink() : xiaodi2.createMakeDrink();
            Snack snack = buySnack() == 0 ? 
                xiaodi1.createMakeSnack() : xiaodi2.createMakeSnack();
            drink.drink();
            snack.snack();
        }
    }

    /* 模擬用戶點飲料,0代表要奶茶,1代表要果汁 */
    private static int buyDrink() { return new Random().nextInt(2); }

    /* 模擬用戶點小吃,0代表要手抓餅,1代表要魚蛋 */
    private static int buySnack() { return new Random().nextInt(2); }
}

輸出結果

兩個名詞(產品等級結構產品族)

  • 產品等級結構(繼承)
    比如這裏的抽象類是Drink(飲料),子類有
    奶茶,果汁,然後抽象飲料與具體飲料構成了一個產品等級
    結構,抽象飲料是父類,具體飲料是其子類。

  • 產品族
    同一工廠生產的,位於不同產品等級結構的一組產品,比如這裏
    的奶茶和果汁屬於飲料結構的一組產品,而手抓餅和魚蛋則
    屬於小吃結構的一組產品。

四個角色與UML類圖

抽象工廠模式適用於創建的對象有多個相互關聯或依賴的產品族
抽象工廠模式隔離具體類的生成,接口與實現分離,增加新的產品族很方便;
但是擴展新的產品等級結構麻煩,需要修改抽象工廠,具體工廠類也要更改。


七. 適配器模式(Adapter Pattern)

兩個彼此間沒太大關聯的類,想進行交互完成某些事情,不想直接
去修改各自的接口,可以添加一個中間類,讓他來協調兩個類
間的關係,完成相關業務,這種模式就叫適配器模式。

然後分爲:類適配器對象適配器 兩種,前者和適配者是繼承關係,
後者與適配者則是引用關係。

對象適配器支持傳入一個被適配器對象,因此可以做到對多種被適
配接口進行適配。而類適配器直接繼承無法動態修改,所以一般情況
下對象適配器使用得更多!(Java不支持多重繼承!!!)

對象適配器例子(用得較多)

/* 目標接口 */
interface Chinese {
    void speakChinese(String string);
}

/* 需要適配的類 */
class English {
    void speakEnglish(String string) { System.out.println("【英語】" + string); }
}

/* 適配器 */
class Translator implements Chinese{
    private English english = new English();

    Translator(English english) { this.english = english; }

    @Override public void speakChinese(String string) { english.speakEnglish(string); }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Chinese chinese = new Translator(new English());
        chinese.speakChinese("那你很棒棒哦!");
    }
}

輸出結果

類適配器例子

/* 類適配器 */
class ClassTranslator extends English implements Chinese {
    @Override public void speakChinese(String string) { speakEnglish(string); }
}

/* 客戶端調用 */
public class ClientC {
    public static void main(String[] args) {
        ClassTranslator translator = new ClassTranslator();
        translator.speakChinese("你也很好啊!");
    }
}

輸出結果

除此之外還有個缺省適配器模式的名詞,簡單點說就是不需要實現接口中
提供的所有方法時,先寫一個抽象類實現這個接口,然後爲每個方法提供一個
默認實現(空方法),然後選擇性覆蓋某些方法實現需求,又稱單接口適配器模式。


八.裝飾者模式(Decorator Pattern)

動態的給對象添加一些額外的職責,就增加功能來說,裝飾者
模式比起生成子類更加靈活!就是想替代多重層繼承的模式。
其實就是一層套一層

代碼示例

/* 抽象組件 */
abstract class Tea {
    private String name = "茶";

    public String getName() { return name; }

    void setName(String name) { this.name = name; }

    public abstract int price();
}

/* 具體組件 */
class MilkTea extends Tea {
    MilkTea() { setName("奶茶"); }

    @Override public int price() { return 5; }
}

class LemonTea extends Tea{
    LemonTea() { setName("檸檬茶"); }

    @Override public int price() { return 3; }
}

/* 抽象裝飾類 */
abstract class Decorator extends Tea{
    public abstract String getName();
}

/* 具體裝飾類 */
class ZhenZhu extends Decorator {
    Tea tea;

    ZhenZhu(Tea tea) { this.tea = tea; }

    @Override public String getName() { return "珍珠" + tea.getName(); }

    @Override public int price() { return 2 + tea.price(); }
}

class YeGuo extends Decorator{
    //...
}

class JinJu extends Decorator{
    //...
}

class HongDou extends Decorator{
    //...
}

/* 客戶端調用 */
public class Store {
    public static void main(String[] args) {
        Tea tea1 = new MilkTea();
        System.out.println("你點的是:" + tea1.getName() + " 價格爲:" + tea1.price());

        Tea tea2 = new LemonTea();
        tea2 = new JinJu(tea2);
        System.out.println("你點的是:" + tea2.getName() + " 價格爲:" + tea2.price());

        Tea tea3 = new MilkTea();
        tea3 = new ZhenZhu(tea3);
        tea3 = new YeGuo(tea3);
        tea3 = new HongDou(tea3);
        tea3 = new JinJu(tea3);
        System.out.println("你點的是:" + tea3.getName() + " 價格爲:" + tea3.price());
    }
}

輸出結果


九.組合模式(Composite Pattern)

部分-整體模式,把具有 相似的一組對象 當做一個對象處理,
用一種 樹狀的結構組合對象,再提供統一的方法去訪問相似的對象,
以此忽略掉對象與對象容器間的差別。

根節點枝結點葉子結點 三個名詞需要理解,
類比上圖,根節點是菜單,枝結點是飲料菜單和小吃菜單,
葉子結點是奶茶,果汁,手抓餅和魚蛋!

代碼示例

/* 抽象組件 */
abstract class AbstractMenu {
    public abstract void add(AbstractMenu menu);
    public abstract AbstractMenu get(int index);
    public abstract String getString();
}

/* 容器組件 */
class Menu extends AbstractMenu {
    private String name;
    private String desc;
    private List<AbstractMenu> menus = new ArrayList<>();

    Menu(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    @Override public void add(AbstractMenu menu) { menus.add(menu); }

    @Override public AbstractMenu get(int index) { return menus.get(index); }

    @Override public String getString() {
        StringBuilder sb = new StringBuilder("\n【菜單】:" + name + " 信息:" + desc + "\n");
        for (AbstractMenu menu: menus) { sb.append(menu.getString()).append("\n"); }
        return sb.toString();
    }
}

/* 葉子組件 */
class MilkTea extends AbstractMenu {
    private String name;
    private String desc;
    private int price;

    MilkTea(String name, String desc, int price) {
        this.name = name;
        this.desc = desc;
        this.price = price;
    }

    @Override public void add(AbstractMenu menu) { /*未使用*/ }

    @Override public AbstractMenu get(int index) { return null; }

    @Override public String getString() {
        return " - 【奶茶】* " + name + " 標註:" + desc + " 價格:" + price;
    }
}

class MilkTea extends AbstractMenu {
    //...
}

class HandCake  extends AbstractMenu {
    //...
}

class FishBall  extends AbstractMenu {
    //...
}

/* 客戶端調用 */
public class Store {
    public static void main(String[] args) {
        AbstractMenu mainMenu = new Menu("大菜單", "包含所有子菜單");
        AbstractMenu drinkMenu = new Menu("飲品菜單", "都是喝的");
        AbstractMenu eatMenu = new Menu("小吃菜單", "都是吃的");
        AbstractMenu milkTea = new MilkTea("珍珠奶茶", "奶茶+珍珠", 5);
        AbstractMenu juice = new Juice("鮮榨獼猴桃枝", "無添加即榨", 8);
        AbstractMenu ball = new FishBall("咖喱魚蛋", "微辣", 6);
        AbstractMenu cake = new HandCake("培根手抓餅", "正宗臺灣風味", 8);

        drinkMenu.add(milkTea);
        drinkMenu.add(juice);
        eatMenu.add(ball);
        eatMenu.add(cake);
        mainMenu.add(drinkMenu);
        mainMenu.add(eatMenu);

        System.out.println(mainMenu.getString());
    }
}

輸出結果


十.橋接模式(Bridge Pattern)

基於單一職責原則,如果系統中的類存在多個變化的維度
通過該模式可以將這幾個維度分離出來, 然後進行獨立擴展。
這些分離開來的維度,通過在抽象層持有其他維度的引用來進行關聯,
就好像在兩個維度間搭了橋一樣,所以叫橋接模式。

代碼示例(變化的三個維度:配餐,扒類)

/* 抽象部分 */
abstract class Rations {
    abstract String rations();
}

/* 擴展抽象部分 */
class Rice extends Rations {
    @Override public String rations() { return "飯"; }
}

class Spaghetti extends Rations {
    @Override public String rations() { return "意粉"; }
}

/* 實現部分 */
abstract class Steak {
    Rations rations;

    Steak(Rations rations) { this.rations = rations; }

    abstract String sale();
}

/* 具體實現部分 */
class BeefSteak extends Steak{
    BeefSteak(Rations rations) { super(rations); }

    @Override public String sale() { return "牛扒"+ (rations == null ? "" : rations.rations()); }
}

class PorkSteak extends Steak {
    PorkSteak(Rations rations) { super(rations); }

    @Override public String sale() { return "豬扒"+ (rations == null ? "" : rations.rations()); }
}


/* 客戶端調用 */
public class Restaurant {
    public static void main(String[] args) {
        System.out.println("\n" + new Date(System.currentTimeMillis()));
        System.out.println("==================");

        Steak steak1 = new BeefSteak(new Rice());
        System.out.println("賣出了一份:" + steak1.sale());

        Steak steak2 = new PorkSteak(new Spaghetti());
        System.out.println("賣出了一份:" + steak2.sale());

        Steak steak3 = new PorkSteak(null);
        System.out.println("賣出了一份:" + steak3.sale());

         System.out.println("==================");
}

輸出結果


十一.外觀模式(Facade Pattern)

要求一個子系統的外部與內部的通信必須通過一個統一的對象進行,
外觀模式提供一個高層次的接口,使得子系統更易於使用。
(其實就是封裝,用於解決類與類間的依賴關係,比如本來是:
玩家依賴於:Q,A,E,R等鍵位對象,現在變成只依賴與腳本對象
從而降低了類間的耦合度。)

代碼示例

/* 子系統 */
class A {
    String a() { return "A"; }
}

class Q { /* ... */ }

class Space { /* ... */ }

class LeftClick { /* ... */ }

/* 外觀類 */
class JiaoBen {
    A a;
    Q q;
    LeftClick leftClick;
    Space space;

    JiaoBen() {
        a = new A();
        leftClick = new LeftClick();
        q = new Q();
        space = new Space();
    }

    String 銳雯() {
        StringBuilder sb = new StringBuilder();
        sb.append(q.q()).append(" + ");
        sb.append(space.space()).append(" + ");
        sb.append(a.a()).append(" + ");
        sb.append(leftClick.leftClick()).append(" + ");
        sb.append(q.q()).append(" + ");
        sb.append(space.space()).append(" + ");
        sb.append(a.a()).append(" + ");
        sb.append(leftClick.leftClick()).append(" + ");
        sb.append(q.q()).append(" + ");
        sb.append(space.space()).append(" + ");
        sb.append(a.a()).append(" + ");
        sb.append(leftClick.leftClick()).append("\n");
        return sb.toString();
    }
}

/* 客戶端調用 */
public class XLoLer {
    public static void main(String[] args) {
        JiaoBen jiaoBen = new JiaoBen();
        System.out.println("=== 銳雯一鍵光速QA ===\n" + jiaoBen.銳雯());
    }
}

輸出結果


十二. 享元模式(Flyweight Pattern)

當存在多個相同對象時,可以使用享元模式減少相同對象創建引起的內存消耗,
提高程序性能。說到共享,還分內部狀態與外部狀態

內部狀態固定不變可共享的的部分,存儲在享元對象內部,比如例子中的花色
外部狀態可變不可共享的部分,一般由客戶端傳入享元對象內部,比如例子裏的大小

示例代碼

/* 抽象對象的父類 */
abstract class Card {
    abstract void showCard(String num);  //傳入外部狀態參數,大小
}

/* 具體享元對象 */
public class SpadeCard extends Card{
    public SpadeCard() { super(); }

    @Override public void showCard(String num) { System.out.println("黑桃:" + num); }
}

public class HeartCard extends Card { /* ... */ }

public class ClubCard extends Card { /* ... */ }

public class DiamondCard extends Card { /* ... */ }

/* 享元工廠 */
public class PokerFactory {
    static final int Spade = 0;  //黑桃
    static final int Heart  = 1; //紅桃
    static final int Club  = 2; //梅花
    static final int Diamond  = 3;   //方塊

    public static Map<Integer, Card> pokers = new HashMap<>();

    public static Card getPoker(int color) {
        if (pokers.containsKey(color)) {
            System.out.print("對象已存在,對象複用...");
            return pokers.get(color);
        } else {
            System.out.print("對象不存在,新建對象...");
            Card card;
            switch (color) {
                case Spade: card = new SpadeCard(); break;
                case Heart: card = new HeartCard(); break;
                case Club: card = new ClubCard(); break;
                case Diamond: card = new DiamondCard(); break;
                default: card = new SpadeCard(); break;
            }
            pokers.put(color,card);
            return card;
        }
    }


/* 客戶端調用 */
public class Player {
    public static void main(String[] args) {
        for (int k = 0; k < 10; k ++){
            Card card = null;
            //隨機花色
            switch ((int)(Math.random()*4)) {
                case 0: card = PokerFactory.getPoker(PokerFactory.Spade); break;
                case 1: card = PokerFactory.getPoker(PokerFactory.Heart); break;
                case 2: card = PokerFactory.getPoker(PokerFactory.Club); break;
                case 3: card = PokerFactory.getPoker(PokerFactory.Diamond); break;
            }
            if(card != null) {
                //隨機大小
                int num = (int)(Math.random()*13 + 1);
                switch (num) {
                    case 11: card.showCard("J"); break;
                    case 12: card.showCard("Q"); break;
                    case 13: card.showCard("K"); break;
                    default: card.showCard(num+""); break;
                }
            }
        }
    }
}

輸出結果


十三.代理模式(Proxy Pattern)

引用代理對象的方式來訪問目標對象,簡單點說,就是在調用某個對象
時加了一層,然後你可以在這一層做些手腳,比如權限控制,或者附加操作等。

代碼示例

/* 抽象對象 */
public interface FetchGoods {
    public void fetchShoes();
}

/* 真實對象 */
public class Custom implements FetchGoods{
    @Override public void fetchShoes() { System.out.println("拿貨"); }
}

/* 代理對象 */
public class Agent implements FetchGoods{
    @Override public void fetchShoes() {
        Custom custom = new Custom();
        custom.fetchShoes();
        this.callCustom();
    }

    public void callCustom() { System.out.println("通知顧客過來取件!"); }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Agent agent = new Agent();
        agent.fetchShoes();
    }
}

輸出結果


十四.策略模式(Strategy Pattern)

定義一系列的算法,把每個算法封裝起來,並使得他們可以相互替換
讓算法獨立於使用它的客戶而變化。 一般用來替換if-else,個人感覺是
面向過程與面向對象思想的過渡。

代碼示例:(面向過程與面向對象的簡易計算器)

面向過程簡易計算器

public class Calculator {
    public static void main(String[] args) {
        System.out.println("計算:1 + 1 = " + compute("+", 1, 1));
        System.out.println("計算:1 - 1 = " + compute("-", 1, 1));
        System.out.println("計算:1 * 1 = " + compute("*", 1, 1));
        System.out.println("計算:1 ? 1 = " + compute("/", 1, 1));
    }

    public static float compute(String operator, int first, int second) {
        switch (operator) {
            case "+": return first + second;
            case "-": return first - second;
            case "*": return first * second;
            case "/": return first / second;
            default: return 0.0f;
        }
    }
}

面向對象(策略模式)簡易計算器

/* 抽象策略類 */
public interface Compute {
    String compute(int first, int second);
}

/* 具體策略類 */
public class Add implements Compute{
    @Override public String compute(int first, int second) {
        return "輸出結果:" + first + " + " + second + " = " + (first + second);
    }
}

public class Sub implements Compute{ /* ... */ }

public class Mul implements Compute{ /* ... */ }

public class Div implements Compute{ /* ... */ }

/* 上下文環境類 */
public class Context {
    private Compute compute;

    public Context() { compute = new Add(); }

    public void setCompute(Compute compute) { this.compute = compute; }

    public void calc(int first, int second) { 
        System.out.println(compute.compute(first, second)); 
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Context context = new Context();

        context.setCompute(new Add());
        context.calc(1,2);

        context.setCompute(new Sub());
        context.calc(3,4);

        context.setCompute(new Mul());
        context.calc(5,6);

        context.setCompute(new Div());
        context.calc(7,8);
    }
}

輸出結果

      


十五.觀察者模式(Observer Pattern)

定義對象見的一種一對多依賴關係,當一個對象的狀態發生改變時
所有依賴於它的對象都得到通知並且自動更新

當對象間存在一對多關係的時候,使用觀察者模式,當一個對象
(被觀察者)被修改時,會自動通知它的依賴對象們(觀察者)。

這個模式基本都應該用過和聽說過,關於概念就不多解釋了,有興趣看原文去~

代碼示例

/* 抽象觀察者 —— 昆蟲類 */
public interface Insect {
    void work();
    void unWork();
}

/* 具體觀察者 —— 蜜蜂類 */
public class Bee implements Insect{
    private int bId;    //蜜蜂編號
    public Bee(int bId) { this.bId = bId; }
    @Override public void work() { System.out.println("蜜蜂"+ bId + "採蜜"); }
    @Override public void unWork() { System.out.println("蜜蜂"+ bId + "回巢"); }
}

/* 抽象被觀察者(註冊,移除,通知觀察者) —— 植物類 */
public interface Plant {
    public void registerInsect(Insect insect);
    public void unregisterInsect(Insect insect);
    public void notifyInsect(boolean isOpen);
}

/* 具體被觀察者(定義一個集合存儲觀察者,實現相關方法) —— 花朵類 */
public class Flower implements Plant {
    private boolean state;
    private List<Insect> insects = new ArrayList<>();
    public boolean isState() { return state; }

    @Override public void registerInsect(Insect insect) { insects.add(insect); }
    @Override public void unregisterInsect(Insect insect) { insects.remove(insect); }
    @Override public void notifyInsect(boolean isOpen) {
        state = isOpen;
        if (state) {
            System.out.println("花開");
            for (Insect insect : insects) { insect.work(); }
        } else {
            System.out.println("花閉");
            for (Insect insect : insects) { insect.unWork(); }
        }
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        //創建被觀察者
        Plant flower = new Flower();
        //創建三個觀察者
        Insect bee1 = new Bee(1);
        Insect bee2 = new Bee(2);
        Insect bee3 = new Bee(3);
        //註冊觀察者
        flower.registerInsect(bee1);
        flower.registerInsect(bee2);
        flower.registerInsect(bee3);
        //改變被觀察者狀態,先開後合
        flower.notifyInsect(true);
        System.out.println("=== 太陽從東邊到西邊... ===");
        flower.notifyInsect(false);
        //最後解除註冊
        flower.unregisterInsect(bee1);
        flower.unregisterInsect(bee2);
        flower.unregisterInsect(bee3);
    }
}

輸出結果


觀察者模式的推與拉

推方式

被觀察者對象向觀察者推送主題的詳細信息,不管觀察者是否需要
推送的信息通常是被觀察者對象的全部或部分數據。(上面的例子就是推方式)

拉方式

被觀察者對象再通知觀察者時,只傳遞少量信息。如果觀察者需要更
詳細的信息,可以主動到被觀察者中獲取,相當於觀察者從被觀察者
中拉取數據。一般的套路是:**把主題對象自身通過update()方法傳遞
給觀察者,然後觀察者在需要獲取的時候,通過這個引用來獲取**。

代碼示例

微信訂閱了某個公衆號,當有更新的時候會推送提醒,收到提醒後,
我們需要進入公衆號然後點擊對應信息查看詳細內容。

/* 抽象觀察者 —— 用戶 */
public interface User {
    public void update(OfficialAccount account);
}

/* 具體觀察者 —— Android讀者 */
public class AndroidDev implements User {
    @Override public void update(OfficialAccount account) {
        System.out.println("讀者查看公衆號更新信息:" +  ((CoderPig)account).getMsg());
    }
}

/* 抽象被觀察者 —— 公衆號 */
public abstract class OfficialAccount {
    private List<User> userList = new ArrayList<>();
    public void registerUser(User user) { userList.add(user); }
    public void unregisterUser(User user) { userList.remove(user); }
    public void notifyUse() {
        for (User user: userList) {
            user.update(this);
        }
    }
}

/* 具體被觀察者 —— CoderPig公衆號 */
public class CoderPig extends OfficialAccount {
    private String msg; //更新的文章
    public String getMsg() { return msg; }
    public void update(String msg) {
        this.msg = msg;
        System.out.println("公衆號更新了文章:" + msg);
        this.notifyUse();   //通知用戶有更新
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        OfficialAccount account = new CoderPig();
        User user = new AndroidDev();
        account.registerUser(user);
        ((CoderPig)account).update("《觀察者模式》");
        account.unregisterUser(user);
    }
}

輸出結果


Java中對觀察者模式的支持(在Java.util中)

口訣被觀察者實現繼承Observable觀察者實現Observer接口
然後有個很關鍵的地方:當通知變化的時候,需要調用setChange()方法!!!!
不用自己另外去寫抽象觀察者或抽象被觀察者類,直接繼承就能玩了,
另外有一點要注意的是:Java內置的觀察者模式通知多個觀察者的順序
不是固定的,如果對通知順序有所依賴的話,還是得自己實現觀察者
模式!

代碼示例

/* 具體觀察者 */
public class AndroidDev implements Observer{
    @Override public void update(Observable o, Object object) {
        System.out.println("收到公衆號更新信息:" + object);
    }
}

/* 具體被觀察者 */
public class CoderPig extends Observable {
    private String msg;
    public String getMsg() { return msg; }

    public void update(String msg) {
        this.msg = msg;
        System.out.println("公衆號更新了文章:" + msg);
        this.setChanged();  //這句話必不可少,通知改變
        this.notifyObservers(this.msg); //這裏用推的方式
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        CoderPig coderPig = new CoderPig();
        AndroidDev dev = new AndroidDev();
        coderPig.addObserver(dev);
        coderPig.update("Java中對觀察者模式的支持~");
        coderPig.deleteObserver(dev);
    }
}

輸出結果


十六.迭代器模式(Iterator Pattern)

提供一種方法順序訪問一個容器(聚合)對象各個元素,而又不暴露該對象的內部表示

從上面的定義可以知道,這個模式的使用場景:容器對象中的元素迭代訪問

四個角色

UML類圖

代碼示例

/* 集合中的元素 */
public class Song {
    private String name;
    private String singer;
    public Song(String name, String singer) {
        this.name = name;
        this.singer = singer;
    }

    /* getter和setter方法 */

    @Override public String toString() { 
        return "【歌名】" + name + " - " + singer; 
    }
}

/* 迭代器角色,第一項,下一個,判斷是否能下一個,獲取當前項。 */
public interface Iterator {
    Song first();
    Song next();
    boolean hashNext();
    Song currentItem();
}

/* 抽象容器,定義一個生成迭代器的方法 */
interface SongList {
    Iterator getIterator();
}

/* 具體容器,繼承抽象容器,並定義一個具體迭代器內部類 */
public class MyStoryList implements SongList{
    private List<Song> list = new ArrayList<>();

    public MyStoryList(List<Song> list) {
        this.list = list;
    }

    @Override public Iterator getIterator() {
        return new SongListIterator();
    }

    private class SongListIterator implements Iterator {
        private int cursor;

        @Override public Song first() {
            cursor = 0;
            return list.get(cursor);
        }

        @Override public Song next() {
            Song song = null;
            cursor++;
            if(hashNext()) {
                song = list.get(cursor);
            }
            return song;
        }

        @Override public boolean hashNext() {
            return !(cursor == list.size());
        }

        @Override public Song currentItem() {
            return list.get(cursor);
        }
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        List<Song> list = new ArrayList<>();
        list.add(new Song("空白格","楊宗緯"));
        list.add(new Song("那時候的我","劉惜君"));
        list.add(new Song("黑澤明","陳奕迅"));
        list.add(new Song("今天只做一件事","陳奕迅"));
        list.add(new Song("童話鎮","陳一發兒"));

        MyStoryList songList = new MyStoryList(list);

        Iterator iterator = songList.getIterator();

        while (iterator.hashNext()) {
            System.out.println(iterator.currentItem().toString());
            iterator.next();
        }
    }
}

輸出結果

PS:由於容器與迭代器的關係太密切了,所以大多數語言在實現容器的時候都給提供了
迭代器,並且這些語言提供的容器和迭代器在絕大多數情況下就可以滿足我們的需要,
所以現在需要我們自己去實踐迭代器模式的場景還是比較少見的,我們只需要使用語言
中已有的容器和迭代器就可以了。


十七.命令模式(Command Pattern)

使用場景行爲請求者行爲實現者解耦

定義:將一個請求封裝成一個對象,從而可用不同的請求對客戶端參數化,
對請求排隊或記錄請求日誌,以及支持可撤銷的操作。

代碼示例

/* 元素對象  */
public class Story {
    private String sName;
    private String sUrl;

    public Story(String sName, String sUrl) {
        this.sName = sName;
        this.sUrl = sUrl;
    }

    /*getter和setter方法*/
}

/* 命令執行者 */
public class StoryPlayer {
    private int cursor = 0; //當前播放項
    private int pauseCursor = -1;   //暫停播放項
    private List<Story> playList = new ArrayList<>();   //播放列表

    public void setPlayList(List<Story> list) {
        this.playList = list;
        cursor = 0;
        System.out.println("更新播放列表...");
    }

    public void play() { /* 播放 */ }
    public void play(int cursor) { /* 根據遊標播放 */ }
    public void next() { /* 下一首 */ }
    public void pre() { /* 上一首 */ }
    public void pause() { /* 暫停 */ }

}

/* 抽象命令接口 */
public interface Command { void execute(); }

/* 具體命令類 */
public class SetListCommand implements Command {
    private StoryPlayer mPlayer;
    private List<Story> mList = new ArrayList<>();

    public SetListCommand(StoryPlayer mPlayer) { this.mPlayer = mPlayer; }

    @Override public void execute() { mPlayer.setPlayList(mList); }

    public void setPlayList(List<Story> list) { this.mList = list; }
}

public class PlayCommand implements Command {
    private StoryPlayer mPlayer;

    public PlayCommand(StoryPlayer mPlayer) { this.mPlayer = mPlayer; }

    @Override public void execute() { mPlayer.play(); }
}

public class PlayCommand implements Command { /* ... */ }

public class PauseCommand implements Command { /* ... */ }

public class NextCommand implements Command { /* ... */ }

public class PreCommand implements Command { /* ... */ }


/* 請求者類,調用命令對象執行具體操作 */
public class Invoker {
    private SetListCommand setListCommand;
    private PlayCommand playCommand;
    private PauseCommand pauseCommand;
    private NextCommand nextCommand;
    private PreCommand preCommand;

    public void setSetListCommand(SetListCommand setListCommand) {
        this.setListCommand = setListCommand;
    }

    public void setPlayCommand(PlayCommand playCommand) {
        this.playCommand = playCommand;
    }

    public void setPauseCommand(PauseCommand pauseCommand) {
        this.pauseCommand = pauseCommand;
    }

    public void setNextCommand(NextCommand nextCommand) {
        this.nextCommand = nextCommand;
    }

    public void setPreCommand(PreCommand preCommand) {
        this.preCommand = preCommand;
    }

    /* 設置播放列表 */
    public void setPlayList(List<Story> list) { 
        setListCommand.setPlayList(list);
        setListCommand.execute();
    }
    public void play() { playCommand.execute(); } /* 播放 */
    public void pause() { pauseCommand.execute(); } /* 暫停 */
    public void next() { nextCommand.execute(); }   /* 下一首 */
    public void pre() { preCommand.execute(); }    /* 上一首 */
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        //實例化播放列表
        List<Story> mList = new ArrayList<>();
        mList.add(new Story("白雪公主",""));
        mList.add(new Story("青蛙的願望",""));
        mList.add(new Story("驢和媽",""));
        mList.add(new Story("小青蛙的煩惱",""));
        mList.add(new Story("三字經",""));

        //實例化接收者
        StoryPlayer mPlayer = new StoryPlayer();

        //實例化命令對象
        Command setListCommand = new SetListCommand(mPlayer);
        Command playCommand = new PlayCommand(mPlayer);
        Command pauseCommand = new PauseCommand(mPlayer);
        Command nextCommand = new NextCommand(mPlayer);
        Command preCommand = new PreCommand(mPlayer);

        //實例化請求者
        Invoker invoker = new Invoker();
        invoker.setSetListCommand((SetListCommand) setListCommand);
        invoker.setPlayList(mList);
        invoker.setPlayCommand((PlayCommand) playCommand);
        invoker.setPauseCommand((PauseCommand) pauseCommand);
        invoker.setNextCommand((NextCommand) nextCommand);
        invoker.setPreCommand((PreCommand) preCommand);

        //測試調用
        invoker.play();
        invoker.next();
        invoker.next();
        invoker.next();
        invoker.next();
        invoker.next();
        invoker.pause();
        invoker.play();
    }
}

輸出結果


十八.備忘錄模式(Memento Pattern)

簡單點說,就是存檔保存一個對象在某個時刻的狀態或部分狀態
在未來某個時段需要時將其還原到原來記錄狀態的模式

代碼示例

/* 備忘錄角色 —— 存檔類*/
public class Memento {
    private int hp;
    private int mp;
    private int money;

    public Memento(int hp, int mp, int money) {
        this.hp = hp;
        this.mp = mp;
        this.money = money;
    }

    /*getter和setter方法*/
}

/* 發起人角色 —— 角色類,屬性定義,定義保存與恢復自身狀態的方法 */
public class Character {
    private int hp;
    private int mp;
    private int money;

    public Character(int hp, int mp, int money) {
        this.hp = hp;
        this.mp = mp;
        this.money = money;
    }

    /*getter和setter方法*/

    public void showMsg() {
        System.out.println("當前狀態:| HP:" + hp + " | MP:" + mp + " | 金錢:" + money + "\n");
    }

    //創建一個備忘錄,保存當前自身狀態
    public Memento save() { return new Memento(hp, mp, money); }

    //傳入一個備忘錄對象,恢復內部狀態
    public void restore(Memento memento) {
        this.hp = memento.getHp();
        this.mp = memento.getMp();
        this.money = memento.getMoney();
    }
}

/* 備忘錄管理者角色 —— 只負責備忘錄對象的傳遞! 多個存檔的話可用集合存,根據索引取*/
public class Caretaker {
    private Memento memento;

    public Memento getMemento() { return memento; }

    public void setMemento(Memento memento) { this.memento = memento; }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Caretaker caretaker= new Caretaker();
        Character character = new Character(2000,1000,500);
        //存檔
        System.out.println("=== 存檔中... ===");
        character.showMsg();
        caretaker.setMemento(character.save());

        System.out.println("=== 單挑Boss,不敵,金錢扣除一半... ===");
        character.setHp(0);
        character.setHp(0);
        character.setHp(250);
        character.showMsg();

        //讀檔
        System.out.println("=== 讀取存檔中... ===");
        character.restore(caretaker.getMemento());
        character.showMsg();
    }
}

輸出結果


十九.中介者模式(Mediator Pattern)

用一箇中介對象來封裝一系列的對象交互,使得各對象不需要
顯式的相互引用, 從而使其耦合鬆散,而且可以獨立的改變
他們之間的交互。

中介者持有所有同事引用,然後在裏面做一些邏輯操作,
然後每個同事類持有中介者引用,依次完成交互。

這裏的話需要與前面的外觀模式,代理模式進行區分!

外觀模式結構型,對子系統提供統一的接口,單向,所有請求都委託子系統完成,樹型
代理模式結構型引用代理對象的方式來訪問目標對象單向
中介者模式行爲型,用一個中介對象封裝一系列同事對象的交互行爲雙向一對多星型

UML類圖

代碼示例

/* 抽象中介類,有連接同事進行交互的方法*/
public abstract class Mediator {
    abstract void contact(People people, String msg);
}

/* 抽象同事類,相關屬性,還有一箇中介類的引用,因爲所有同事都知道中介*/
public abstract class People {
    protected String name;
    protected Mediator mediator;    //每個人都知道中介

    public People(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
}

/* 具體同事類,這裏是房東和房客*/
public class Landlord extends People {
    public Landlord(String name, Mediator mediator) { super(name, mediator); }

    public void contact(String msg) { mediator.contact(this, msg); }

    public void getMessage(String msg) { System.out.println("【房東】" + name + ":" + msg); }
}

public class Tenant extends People {
    public Tenant(String name, Mediator mediator) { super(name, mediator); }

    public void contact(String msg) { mediator.contact(this, msg); }

    public void getMessage(String msg) { System.out.println("【房客】" + name + ":" + msg); }
}

/* 具體中介類,中介者知道所有的同事,實現交互方法時對調用者進行判斷
 實現對應的邏輯,比如這裏的信息顯示*/
public class HouseMediator extends Mediator {
    //中介者知道所有同事
    private Landlord landlord;
    private Tenant tenant;

    public Landlord getLandlord() { return landlord; }
    public void setLandlord(Landlord landlord) { this.landlord = landlord; }
    public Tenant getTenant() { return tenant; }
    public void setTenant(Tenant tenant) { this.tenant = tenant; }

    @Override void contact(People people, String msg) {
        if(people == tenant) tenant.getMessage(msg);
        else landlord.getMessage(msg);
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        //實例化中介者
        HouseMediator mediator = new HouseMediator();
        //實例化同事對象,傳入中介者實例
        Landlord landlord = new Landlord("包租婆",mediator);
        Tenant tenant = new Tenant("小豬",mediator);
        //爲中介者傳入同事實例
        mediator.setLandlord(landlord);
        mediator.setTenant(tenant);
        //調用
        landlord.contact("單間500一個月,有興趣嗎?");
        tenant.contact("熱水器,空調,網線有嗎?");
        landlord.contact("都有。");
        tenant.contact("好吧,我租了。");
    }
}

輸出結果


二十.解釋器模式(Interpreter Pattern)

用得比較少的一種模式,定義也比較枯澀難懂,實在不理解可以先看代碼:

給定一個語言之後,解釋器模式可以定義出其文法的一種表示,並同時提供一個
解釋器,客戶端可以使用這個解釋器來解釋這個語言中的句子。

個人理解

定義了一套簡單語法,每個終結符有一個對應的值存起來了,
然後當你輸了一串終結符,最後解釋能得出一個正確結果

代碼示例能夠解釋加減法的解釋器

/* 抽象表達式 */
public abstract class Expression {
    public abstract int interpret(Context context);
    @Override public abstract String toString();
}

/* 非終結符表達式 —— 加法和減法 */
public class PlusExpression extends Expression{
    private Expression leftExpression;
    private Expression rightExpression;

    public PlusExpression(Expression leftExpression, Expression rightExpression) {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    @Override public int interpret(Context context) {
        return leftExpression.interpret(context) + rightExpression.interpret(context);
    }

    @Override public String toString() {
        return leftExpression.toString() + " + " + rightExpression.toString();
    }
}

public class MinusExpression extends Expression{ /* 和減法類似 */ }

/* 終結符表達式 —— 常量與變量*/
public class ConstantExpression extends Expression {
    private int value;

    public ConstantExpression(int value) { this.value = value; }

    @Override public int interpret(Context context) { return value; }

    @Override public String toString() { return Integer.toString(value); }
}

public class VariableExpression extends Expression {
    private String name;

    public VariableExpression(String name) { this.name = name; }

    @Override public int interpret(Context context) { return context.lookup(this); }

    @Override public String toString() { return name; }
}

/* 上下文環境 —— 用Map存放各個終結符對應的具體值*/
public class Context {
    private Map<Expression, Integer> map = new HashMap<>();

    public void addExpression(Expression expression, int value) { map.put(expression, value); }

    public int lookup(Expression expression) { return map.get(expression); }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Context context = new Context();

        VariableExpression a = new VariableExpression("a");
        VariableExpression b = new VariableExpression("b");
        ConstantExpression c = new ConstantExpression(6);

        context.addExpression(a, 2);
        context.addExpression(b, 3);

        Expression expression = new PlusExpression(new PlusExpression(a,b),new MinusExpression(a,c));
        System.out.println(expression.toString() + " = " + expression.interpret(context));
    }
}

輸出結果


二十一.訪問者模式(Visitor Pattern)

核心數據結構不變操作可變,結構與操作解耦的一種模式。
定義:封裝一些作用域某種數據結構中的個元素的操作,在不改變這個
數據結構的前提下,定義作用於這些元素的新的操作

代碼示例

/* 元素角色 —— 遊戲機接口,有一個傳入訪問者實例的方法 */
public interface Machine {
    public void accept(Player player);
}

/* 具體元素 —— 投籃機,跳舞機和開車 */
public class Shooting implements Machine {
    @Override public void accept(Player player) { player.visit(this); }
    public String feature() { return "投籃機"; }
}

public class Dancing implements Machine { /*...*/ }
public class Driving implements Machine { /*...*/ }

/* 抽象訪問者 —— 定義元素對應的訪問方法,傳入相應實例*/
public interface Player {
    public void visit(Shooting machine);
    public void visit(Dancing machine);
    public void visit(Driving machine);
}

/* 具體訪問者 —— 男女性玩家 */
public class MalePlayer implements Player{
    @Override public void visit(Shooting machine) { 
        System.out.println("男性玩家玩:" + machine.feature());
    }

    @Override public void visit(Dancing machine) {
        System.out.println("男性玩家玩:" + machine.feature());
    }

    @Override public void visit(Driving machine) {
        System.out.println("男性玩家玩:" + machine.feature());
    }
}

public class FemalePlayer implements Player{ /*...*/ }

/* 對象結構 —— 管理元素集合,並且可迭代訪問者訪問 */
public class GameRoom  {
    private List<Machine> machines = new ArrayList<>();
    public void add(Machine machine) { machines.add(machine); }
    public void action(Player player) {
        for (Machine machine: machines) { machine.accept(player); }
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        GameRoom room = new GameRoom();
        room.add(new Shooting());
        room.add(new Dancing());
        room.add(new Driving());

        Player player1 = new MalePlayer();
        Player player2 = new FemalePlayer();

        room.action(player1);
        room.action(player2);
    }
}

輸出結果


二十二.責任鏈模式(Chain of Responsibility Pattern)

使多個對象都有機會處理請求,從而避免請求的發送者與接收者之間的耦合關係,
將這個對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
(典型代表:Android裏的事件傳遞機制)

代碼示例

/* 抽象處理者 */
public abstract class Handler {
    private Handler nextHandler; /* 下家處理者 */

    public Handler getNextHandler() { return  nextHandler; }

    public void setNextHandler(Handler nextHandler) { this.nextHandler = nextHandler; }

    public abstract void handlerRequest(String str, int money);   /* 請求 */
}

/* 具體處理者 —— 哥哥,爸爸,媽媽,依次傳遞*/
public class Brother extends Handler {
    @Override public void handlerRequest(String str, int money) {
        if(money <= 100) {
            System.out.println("哥哥:100塊,哥哥還是有的,給你~");
        } else {
            if(getNextHandler() != null) {
                System.out.println("哥哥:大於100塊,哥哥木有那麼多錢,找粑粑去~");
                getNextHandler().handlerRequest(str, money);
            } else {
                System.out.println("哥哥:大於100塊,哥哥木有那麼多錢,粑粑不在家~");
            }
        }
    }
}

public class Father extends Handler {
    @Override public void handlerRequest(String str, int money) {
        if(money <= 500) {
            System.out.println("粑粑:500塊,粑粑還是有的,給你~");
        } else {
            if(getNextHandler() != null) {
                System.out.println("粑粑:大於500塊,粑粑木有那麼多錢,找麻麻去~");
                getNextHandler().handlerRequest(str, money);
            } else {
                System.out.println("粑粑:大於500塊,粑粑木有那麼多錢,麻麻不在家~");
            }
        }
    }
}

public class Mother extends Handler {
    @Override public void handlerRequest(String str, int money) {
        if(money <= 1000) {
            System.out.println("麻麻:1000塊,麻麻還是有的,給你~");
        } else {
            System.out.println("麻麻:你拿那麼多錢幹嘛?");
        }
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Brother brother = new Brother();
        Father father = new Father();
        Mother mother = new Mother();

        //指定下家
        brother.setNextHandler(father);
        father.setNextHandler(mother);

        brother.handlerRequest("要錢",1200);
    }
}

輸出結果

另外責任鏈模式還分純與不純

  • 純責任鏈,要麼承擔全部責任,要麼責任推個下家,不允許在某處承擔部分
    或者全部責任,然後又把責任推給下家

  • 不純責任鏈,責任在某處部分或全部被處理後,還向下傳遞。


二十三.狀態模式(State Pattern)

定義:當一個對象的內在狀態發生改變時允許改變其行爲,這個對象看起來像
是改變了它的類。

套路

抽象出狀態State,然後實現該接口,然後具體化不同狀態,
做不同的操作,然後寫一個Context,裏面存儲一個State的實例,然後定義一個
可以修改State實例的方法,並在裏面去調用實例的行爲方法。

示例代碼

/* 抽象狀態 */
public interface State {
    public void doSomeThing();
}

/* 具體狀態 */
public class MorningState implements State {
    @Override public void doSomeThing() { System.out.println("早上賴牀!"); }
}

public class AfternoonState implements State {
    @Override public void doSomeThing() { System.out.println("下午學習!"); }
}

public class EveningState implements State {
    @Override public void doSomeThing() { System.out.println("晚上打球!"); }
}

/* 上下文環境 */
public class Context {
    public void setState(State state) {
        System.out.println("狀態改變");
        state.doSomeThing();
    }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        MorningState morningState = new MorningState();
        AfternoonState afternoonState = new AfternoonState();
        EveningState eveningState = new EveningState();

        Context context = new Context();
        context.setState(morningState);
        context.setState(afternoonState);
        context.setState(eveningState);
    }
}

輸出結果


二十四.模板方法模式(Template Method Pattern)

定義:定義一個操作中的算法的框架,而將一些步驟延遲到子類中,
使得子類可以不改變一個算法的結構即可衝定義該算法的某些特定步驟。
(定義比較抽象,舉之前工廠模式做奶茶的例子幫助理解下)

奶茶的製作步驟:加奶,加茶,加料,打包

在這幾個步驟中,加奶,加茶,加料這幾步是固定,而打包的話則是不固定的
有些用戶喜歡找個小桌子喝喝奶茶吹吹水,有些喜歡帶回家,存在可變性。

對於這種可變的步驟,可以使用一個叫做鉤子的東西,其實就是一種被聲明
在抽象類的方法,可以爲空或者默認的實現。鉤子的存在可以讓子類有能力對算
法的不同點進行掛鉤,是否需要掛鉤由子類決定。比如例子通過一個標記確定是
否需要打包,子類中可以定義一個方法來調用這個方法。

代碼示例

/* 抽象模板 */
public abstract class Tea {
    protected void 加奶() { System.out.println("加入三花淡奶"); }
    protected abstract void 加茶();
    protected abstract void 加料();
    protected  void 打包() { System.out.println("用打包機打包"); }

    protected boolean 是否打包() { return true; }     //鉤子方法

    public final void make() {
        System.out.println("=== 開始製作 ===");
        加奶();
        加茶();
        加料();
        if(是否打包()) { 打包(); }
        System.out.println("=== 製作完畢 ===");
    }
}

/* 具體模板 */
public class RedTeaMilkTea extends Tea {
    @Override protected void 加茶() { System.out.println("加入紅茶"); }
    @Override protected void 加料() { System.out.println("加入珍珠"); }
}

public class GreenTeaMilkTea extends Tea {
    private boolean isPack = true;
    public GreenTeaMilkTea(boolean isPack) { this.isPack = isPack; }

    @Override protected void 加茶() { System.out.println("加入綠茶"); }
    @Override protected void 加料() { System.out.println("加入椰果"); }
    @Override protected boolean 是否打包() { return isPack; }
}

/* 客戶端調用 */
public class Client {
    public static void main(String[] args) {
        Tea tea1 = new RedTeaMilkTea();
        Tea tea2 = new GreenTeaMilkTea(false);

        tea1.make();
        System.out.println("");
        tea2.make();
    }
}

輸出結果


To be continue


附錄

本文在線版(排版更佳,優先更新):https://www.zybuluo.com/coder-pig/note/658810
本文不收取任何費用,歡迎轉載,請勿將本文用於商業用途,
想了解更多內容可見:http://blog.csdn.net/coder_pig
如果本文對你學習設計模式有一定幫助,不妨小額打賞下小豬,你的鼓勵是我不斷寫
博客的動力,當然實在囊中羞澀,但又很想支持小豬的話,點個贊,留個言也行,
萬分感謝~

微信: 支付寶:

如有什麼疑問歡迎加羣:421858269 反饋,謝謝~


編輯日誌:

  • 2017.5.9:補全剩餘設計模式,文字排版優化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章