天天看點

架構師日記 | 深入了解軟體設計模式

作者:京東雲開發者

一 設計模式與程式設計語言

1.1 什麼是設計模式

設計模式(Design pattern) :由軟體開發人員在軟體開發中面臨常見問題的解決方案,是經過長時間的試驗積累總結出來的,它使設計更加靈活和優雅,複用性更好。從實用的角度來看,它代表了某一類問題的最佳實踐。

設計模式到底解決了開發過程中的哪些難題呢,它又是如何來解決的呢?

其核心是:複用和解耦。使不穩定依賴于穩定、具體依賴于抽象,以此增強軟體設計适應變化的能力。

1.2 什麼是程式設計範式

要探讨設計模式和程式設計語言的關系,還得從程式設計範式談起。程式設計範式一詞最早來自 Robert Floyd 在 1979 年圖靈獎的頒獎演說,是程式員看待程式的觀點,代表了程式設計者認為程式應該如何被建構和執行的看法,與軟體模組化方式和架構風格有緊密關系。

目前主流的程式設計範式有三種:

1.結構化程式設計(structured programming)

2.面向對象程式設計(object-oriented programming)

3.函數式程式設計(functional programming)

這幾種程式設計範式之間的關系如下:

1.起初是非結構化程式設計,指令(goto指令)可以随便跳轉,資料可以随便引用。後來有了結構化程式設計,人們把 goto 語句去掉了,限制了指令的方向性,過程之間是單向的,但資料卻是可以全局通路的;

2.後來面向對象程式設計的時候,人們幹脆将資料與其緊密耦合的方法放在一個邏輯邊界内,限制了資料的作用域,靠關系來查找;

3.到函數式程式設計的時候,人們限制了資料的可變性,通過一系列函數的組合來描述資料,從源到目标映射規則的編排,中間它是無狀态的;

程式設計範式是抽象的,程式設計語言是具體的。程式設計範式是程式設計語言背後的思想,要通過程式設計語言來展現。C 語言的主流程式設計範式是結構化程式設計,而 Java 語言的主流程式設計範式是面向對象程式設計,後來 Java8 開始支援 Lambda 表達式,将函數式程式設計範式的内容融合進來,同時新誕生的語言一開始就支援多範式,比如 Scala,Go 和 Rust 等。

從結構化程式設計到面向對象程式設計,再到函數式程式設計,抽象程度越來越高(離圖靈機模型越來越遠),與領域問題的距離越來越近。直覺的來講,就是解決現實問題的效率提升了,靈活性和執行效率随之有所下降。

設計模式無論用什麼語言實作都是可以的,然而由于語言的各自差異化特點,不是每種語言都完美或統一實作各種設計模式。比如Java裡面有政策模式,那是因為Java8之前不支援方法傳遞,不能把一個方法當作參數傳給别人,是以有了政策模式。而JavaScript等語言可以直接傳函數,就根本沒必要造一個政策模式出來。

1.3 什麼是多态特性

面向對象程式設計語言有三大特性:封裝、繼承和多态。

1.封裝即資訊隐藏或資料保護,“資料結構"通過暴露有限的通路接口,授權外部僅能通過"資料結構"提供的方法(函數)來通路其内部的資料;

2.繼承的好處是可以實作代碼複用,但不應過度使用,如果繼承的層次過深就會導緻代碼可讀性和可維護性變差。 是以建議少用繼承而多用組合模式;

3.多态可以分為變量的多态,方法的多态,類的多态。通常強調的是類的多态,多态的實作是指子類可以替換父類,在實際代碼運作過程中調用子類的方法實作;

多态可以說是面向對象中最重要的一個特性,是解決項目中緊偶合的問題,提高代碼的可擴充性和可複用性的核心,是很多設計模式、設計原則、程式設計技巧的代碼實作基礎。

多态比較直覺的了解就是去完成某個動作,當不同的對象去完成時會産生出不同的狀态,其作用範圍可以是方法的參數和方法的傳回類型。

多态這種特性也需要程式設計語言提供特殊的文法機制來實作,Java 中多态可以通過"子類繼承父類+子類重寫父類方法+父類引用指向子類對象"的方式實作,還可以通過"接口文法"的方式實作。C++中則使用virtual(虛函數)關鍵字來實作。 像一些動态語言如 Python 也可以通過 duck-typing 的文法實作,另外 Go 語言中的"隐藏式接口"也算是 duck-typing。

Python 語言,實作多态示例如下:

class MyFile:
    def write(self):
        print('I write a message into file.')
        
class MyDB:
    def write(self):
        print('I write data into db. ')
        
def doIt(writer):
    writer.write()

def demo():
    myFile= MyFile()
    myDB = MyDB()
    doIt(myFile)
    doIt(myDB )           

二 設計模式與架構模式

2.1 了解架構模式

對給定上下文的軟體架構中常見問題的一種通用的可複用的解決方案,可以為設計大型軟體系統的各個方面提供相應的指導。它不僅顯示了軟體需求和軟體結構之間的對應關系,而且指定了整個軟體系統的組織和拓撲結構,提供了一些設計決策的基本原理,常見的架構設計模式如下:

架構模式 模式描述 适用場景
分層模式 (Layered pattern) 用于可分解為子任務的結構化程式,每個子任務都位于特定的抽象層級,每一層都為上一層提供服務。 桌面應用程式; 電子商務web應用程式; 移動App;
用戶端-伺服器模式 (Client-server pattern) 伺服器将向多個用戶端提供服務。用戶端從伺服器請求服務,伺服器向這些用戶端提供相關服務。 電子郵件、文檔共享和銀行等線上應用程式; 基于IPC的應用程式;
主從模式 (Master-slave pattern) 主節點将工作配置設定給相同的從節點,并根據從節點傳回的結果計算最終結果。 資料庫主從複制; 程序内多線程排程;
管道-過濾器模式 (Pipe-filter pattern) 用于構造生成和處理資料流的系統。每個處理步驟都包含一個過濾器元件。要處理的資料通過管道傳遞。這些管道可用于緩沖或同步目的。 編譯器;
代理模式 (Broker pattern) 通過解耦元件來構造分布式系統。 消息中間件;; 網絡傳輸中的代理軟體
點對點模式 (Peer-to-peer pattern) 每個元件都稱為對等節點。對等節點既可以作為客戶機(從其他對等節點請求服務),也可以作為伺服器(向其他對等節點提供服務)。 檔案共享網絡; 多媒體協定;
事件-總線模式 (Event-bus pattern) 訂閱釋出模式,事件源将消息釋出到事件總線上的特定通道,監聽者訂閱特定的通道。 通知服務; 注冊中心;
模型-視圖-控制器模式(Model-view-controller pattern) MVC模式,解耦元件并允許有效的代碼重用 web應用程式架構; GUI 應用程式;
黑闆模式 (Blackboard pattern) 對于沒有确定解決方案政策的問題非常有用,所有的元件都可以到達黑闆。元件可以生成添加到黑闆上的新資料對象。元件在黑闆上查找特定類型的資料,并通過與現有的知識源進行模式比對找到這些資料。 語音識别; 車輛識别及追蹤;
解釋器模式 (Interpreter pattern) 用于設計一個解釋專用語言編寫的程式元件。 資料庫查詢語言,如SQL 用于描述通信協定的語言;

2.2 了解設計模式

在1995年,有四位程式設計界的前輩合著了一本書,書名叫做《Design Patterns: Elements of Reusable Object-Oriented Software》,翻譯過來就是《設計模式:可複用面向對象軟體的基礎》,書裡面總共收錄了23種設計模式。這本書是軟體研發領域重要的裡程碑,合著此書的四位作者,被業内稱為GoF(Gang of Four),是以這本書也被人稱為GoF設計模式。

設計模式按照目的來分類有:建立、結構、行為三種,按照作用範圍來分類有:類模式和對象模式兩種。

1.建立型模式:用于建立對象,就是将對象的建立與使用分離。進而降低系統的耦合度,使用者不需要關注對象的建立細節,對象的建立由相關的工廠來完成。

2.結構型模式:描述如何将類,對象,接口之間按某種布局組成更大的結構。

3.行為型模式:用于描述程式在運作時複雜的流程控制,即描述多個類或對象之間怎樣互相協作共同完成單個對象都無法單獨完成的任務,它涉及算法與對象間職責的配置設定。

23種設計模式如下:

類型 模式名稱 模式描述
建立型 單例模式(Singleton) 某個類隻能生成一個執行個體,該類提供了一個全局通路點供外部擷取該執行個體,其拓展是有限多例模式。
工廠方法模式(Factory Method) 定義一個用于建立産品的接口,由子類決定生産什麼産品。
抽象工廠模式(AbstractFactory) 提供一個建立産品族的接口,其每個子類可以生産一系列相關的産品。
建造者模式(Builder) 将一個複雜對象分解成多個相對簡單的部分,然後根據不同需要分别建立它們,最後建構成該複雜對象。
原型模式(Prototype) 将一個對象作為原型,通過對其進行複制而克隆出多個和原型類似的新執行個體。
結構型 擴充卡模式(Adapter) 将一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不相容而不能一起工作的那些類能一起工作。
橋接模式(Bridge) 将抽象與實作分離,使它們可以獨立變化。它是用組合關系代替繼承關系來實作,進而降低了抽象和實作這兩個可變次元的耦合度。
組合模式(Composite) 将對象組合成樹狀層次結構,使使用者對單個對象群組合對象具有一緻的通路性。
裝飾模式(Decorator) 動态的給對象增加一些職責,即增加其額外的功能。
外觀模式(Facade) 為多個複雜的子系統提供一個一緻的接口,使這些子系統更加容易被通路。
亨元模式(Flyweight) 運用共享技術來有效地支援大量細粒度對象的複用。
代理模式(Proxy) 為某對象提供一種代理以控制對該對象的通路。即用戶端通過代理間接地通路該對象,進而限制、增強或修改該對象的一些特性。
行為型 模闆方法模式(TemplateMethod) 定義一個操作中的算法骨架,而将算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。
政策模式(Strategy) 定義了一系列算法,并将每個算法封裝起來,使它們可以互相替換,且算法的改變不會影響使用算法的客戶。
指令模式(Command) 将一個請求封裝為一個對象,使送出請求的責任和執行請求的責任分割開。
職責鍊模式(Chain of Responsibility) 把請求從鍊中的一個對象傳到下一個對象,直到請求被響應為止。通過這種方式去除對象之間的耦合。
狀态模式(State) 允許一個對象在其内部狀态發生改變時改變其行為能力。
觀察者模式(Observer) 多個對象間存在一對多關系,當一個對象發生改變時,把這種改變通知給其他多個對象,進而影響其他對象的行為。
中介者模式(Mediator) 定義一個中介對象來簡化原有對象之間的互動關系,降低系統中對象間的耦合度,使原有對象之間不必互相了解。
疊代器模式(Iterator) 提供一種方法來順序通路聚合對象中的一系列資料,而不暴露聚合對象的内部表示。
通路者模式(Visitor) 在不改變集合元素的前提下,為一個集合中的每個元素提供多種通路方式,即每個元素有多個通路者對象通路。
備忘錄模式(Memento) 在不破壞封裝性的前提下,擷取并儲存一個對象的内部狀态,以便以後恢複它。
解釋器模式(Interpreter) 提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。

2.3 小結

•架構模式更像是宏觀戰略層面的設計,設計模式則更像是戰略目标拆解出來的具體任務的實作方案;

•軟體架構是軟體的一種搭建形式,往往規定了軟體的子產品組成,通信接口(含通信資料結構),元件模型,內建架構等,往往規定了具體的細節;

•設計模式是一種軟體的實作方法,是一種抽象的方法論,是為了更好的實作軟體而歸納出來的有效方法;

•實作一種軟體架構,不同組成部分可能用到不同的設計模式,某個部分也可能可以采用不同的設計模式來實作;

三 應用實踐指南

3.1 适用場景

不使用設計模式也能實作業務訴求,系統也能夠正常運作,為什麼要使用設計模式呢?

是的,相當一部分場景是不需要進行設計模式的引入的,比如:業務邏輯簡單,業務演進方向不明朗,或者就是一個不需要經常疊代的功能點。但當我們遇到了複雜問題設計的時候,就需要借助前人的經驗了,而設計模式就是前人為我們沉澱總結的各種常見問題的解決方案。

那麼多種設計模式,難道我需要全部系統的學習實作一遍,都要閉着眼睛就能寫出來嗎?其實不用,這就跟排序算法一樣,我們隻需要記住每種算法的适用範圍和場景就可以了,在有需要的時候,再去深入研究就可以了。以下總結了各種設計模式對應的适用場景:

模式名稱 适用場景
單例模式(Singleton) 無狀态類使用單例模式可以節省記憶體資源
工廠方法模式(Factory Method) 在不知道具體實作細節的情況下建立對象的場景
抽象工廠模式(AbstractFactory) 用戶端與對象建立解耦,需要建立多個不同類型的對象的場景
建造者模式(Builder) 生成複雜對象的場景
原型模式(Prototype) 快速建立大量同類對象的場景
擴充卡模式(Adapter) 讓兩個不相容的類一起工作的場景
橋接模式(Bridge) 将一個類的抽象部分和實作部分獨立改變的場景
組合模式(Composite) 表示樹形結構的場景
裝飾模式(Decorator) 動态地為對象添加新職責的場景
外觀模式(Facade) 為一個複雜的子系統提供一個簡單的接口的場景
亨元模式(Flyweight) 在多個地方共享大量細粒度對象的場景
代理模式(Proxy) 在通路某個對象時增加額外控制的場景
模闆方法模(TemplateMethod) 在不改變算法結構的情況下重定義算法中的某些步驟的場景
政策模式(Strategy) 在不同情況下使用不同算法的場景
指令模式(Command) 支援指令的撤銷和恢複、延遲調用或日志操作的場景
職責鍊模式(Chain of Responsibility) 在不明确指定接收者的情況下,向多個對象中送出一個請求的場景
狀态模式(State) 根據對象的狀态來改變它的行為的場景。
觀察者模式(Observer) 在對象之間松散耦合的場景
中介者模式(Mediator) 在多個對象之間松散耦合的場景
疊代器模式(Iterator) 為容器對象提供多種周遊方式的場景
通路者模式(Visitor) 在不改變各元素的類的前提下定義對這些元素的新操作的場景
備忘錄模式(Memento) 曆史回放或者復原等場景
解釋器模式(Interpreter) 定義一個語言并為該語言實作一個解釋器的場景

3.2 場景案例

為了讓讀者對設計模式有個更加直覺立體的感覺,接下來以實際案例為大家展現一下設計模式在實際場景的應用。案例包含了建立型,結構型,行為型各種模式類型裡常用的設計模式,比如:

•用工廠模式隔離業務實作;

•用政策模式消解業務流程分支;

•用模闆方法模式提取業務分支公共流程;

•用建造者模式簡化入參對象的建構難度;

•用代理模式橫向擴充通用能力(日志,異常處理);

•用職責鍊模式對請求進行敏感詞,防刷校驗;

•用指令模式讓指令擁有了記憶;

中國有個古諺語:“一個和尚挑水吃,兩個和尚擡水吃,三個和尚等水吃。” 我們就通過程式來模拟出家人的寺廟生活。

工廠模式

首先,這三個人是如何成為和尚的呢?

一号和尚(貧困潦倒型),出生在一個大山裡頭,父母怕他孤單,給他生了5個弟弟,在他9歲那年,恰巧家裡鬧了饑荒,為了吃上飯,進了寺廟,出了家;

二号和尚(走投無路型),出生在一個湖泊旁邊,因為生性耿直,18歲那年,走在街頭,路見不平,三拳打死街上惡霸,為了贖罪,受了戒,墜入空門;

三号和尚(天選之子型),從小敏而好學,性情溫厚,對佛學産生濃厚興趣,13歲那年,為了繼承和光大佛法,斷了塵緣,皈依佛門。

N号和尚,......

每一個和尚的來曆都不盡相同,但在當下喝不上水,這件事情上,都顯得不重要。重要的是,隻要湊足三個和尚,就會沒水喝。那麼寺廟如招收和尚?這裡就可以用到工廠模式的思想。

// 貧困潦倒産生的和尚過程:1.大山裡;2.鬧饑荒;3.要吃飯;
    一号和尚 = HeShangFactoty.getOneHeshang("貧困潦倒型");
    // 走投無路産生的和尚過程:1.生性耿直;2.打死惡霸;3.要贖罪;
    二号和尚 = HeShangFactoty.getOneHeshang("走投無路型");
    // 天選之子産生的和尚過程:1.敏而好學;2.佛學感興趣;3.要廣大佛法;
    三号和尚 = HeShangFactoty.getOneHeshang("天選之子型");           

以上示例想展現的是工廠模式能将複雜的對象建立和使用進行了分離設計。下面就以和尚吃水這件事情,用程式的方式詳細展現工廠模式的實作思路。按照和尚的人數,分别有挑,擡,等三種實作方式。以下為基礎代碼實作:

public interface Waterable {
    Water getWater();
}

public class TiaoShui implements Waterable{
    public Water getWater(){
        System.out.println("先到山下去!");
        return "水是挑上來的!";
    }
} 

public class TaiShui implements Waterable{
    public Water getWater(){
        System.out.println("先到山下去!");
        return "水是擡上來的!";
    }
} 

public class DengShui implements Waterable{
    public Water getWater(){
        System.out.println("就坐在原地!");
        return "水是等不來的!";
    }
}            

具體使用

public class Factory {
    /**
     * 按照和尚數量生成取水對象
     *
     * @param heShangNum 和尚數量
     * @return
     */
    public static Waterable getWaterable(Integer heShangNum) {
        switch (heShangNum) {
            case 1:
                return new TiaoShui();
            case 2:
                return new TaiShui();
            case 3:
                return new DengShui();
            default:
                throw new RuntimeException("廟小,裝不下那麼多和尚!");
        }
    }
}           

政策模式

按照不同的條件(人數),分别有幾種擷取水的方法:挑,擡,等。可以通過政策模式來實作,前面的實作方式其實就是政策模式和工廠模式的結合。我們再看一下政策模式的具體使用方式如下:

/**
     * 通過入參和尚人數,就可以動态改變Waterable.getWater()的取水模式
     * @param heShangNum
     * @return
     */
    public void getWater(Integer heShangNum) {
        Waterable waterable = Factory.getWaterable(heShangNum);
        Water water = waterable.getWater();// 取水
    }           

1.輸入參數1:挑水模式的實作(對應Tiaoshui實作類);

2.輸入參數2:擡水模式的實作(對應Taishui實作類);

3.輸入參數3:等不到水模式的實作(對應Dengshui實作類);

通過和尚人數,就可以動态獲得對應的取水實作,即所謂的通過政策實作業務,對于使用方來說(主流程),無需關注取水的具體實作(解耦:業務流程穩定性的設計展現),新增取水方式時,隻需要新增一個類實作就可以了,存量的實作和主流程都不會受到影響。

模闆方法

我們細化取水過程,取水過程一般需要三步:

1.拿起工具(扁擔或者木棍);

2.到寺廟南面的小河邊(步行);

3.裝滿水帶回寺廟(挑水,擡水,等水);

我們可以将取水流程步驟進行模闆化。

public interface Waterable {
    Water getWater();
}

public abstract class AbstractWaterable implements Waterable {
    @Override
    public Water getWater() {
        takeTool();
        toRiver();
        return moveWater();
    }
    /**
     * 拿起工具
     */
    protected abstract String takeTool();

    /**
     * 到河邊去
     */
    protected String toRiver() {
        System.out.println("走過去!");
        return "步行";
    }

    /**
     * 将水帶回來
     *
     * @return
     */
    protected abstract Water moveWater();
}           

個性化場景實作

public class TiaoShui extends AbstractWaterable {

    @Override
    protected String takeTool() {
        return "扁擔";
    }

    @Override
    protected Water moveWater() {
        return "挑水";
    }
}

public class Taishui extends AbstractWaterable{
    @Override
    protected String takeTool() {
        return "木棍";
    }

    @Override
    protected Water moveWater() {
        return "擡水";
    }
}

public class DengShui extends AbstractWaterable{
    @Override
    protected String takeTool() {
        return "意念";
    }

    @Override
    protected String toRiver() {
        return "一動不動";
    }

    @Override
    protected Water moveWater() {
        return "無水";
    }
}           

具體使用

/**
     * 和尚取水:實作一個和尚挑水喝,兩個和尚擡水喝,三個和尚等水喝
     */
    public void fetchWater(){
        // 
        for (int heShangNum = 1; heShangNum < 4; heShangNum++) {
            Waterable waterable = Factory.getWaterable(heShangNum);
            Water water = waterable.getWater();
        }
    }           

模闆方法講的是流程标準定義和能力複用。示例中,定義了取水的三個階段,選擇工具,出行方式,搬運方式。單看出行方式中,【挑水】和【擡水】複用了模闆方法裡的通用實作,【等水】則個性化的重寫了出行方式。

建造者模式

我們取水需要一些工具,按照取水方式(挑,擡,等)可以分為扁擔+木桶,木棍+木桶,意念(什麼也不需要)等裝備的組合方式。如何定義getWater(ToolBox toolBox)的入參ToolBox,使其能夠按照對應取水方式比對正确的裝備組合呢?這裡就可以使用建造者模式。

public class ToolBox {
    private final String bianDan;
    private final String muTong;
    private final String muGun;

    private ToolBox(TiaoBuilder builder){
        this.bianDan=builder.bianDan;
        this.muTong=builder.muTong;
        this.muGun = null;
    }
    private ToolBox(TaiBuilder builder){
        this.bianDan = null;
        this.muTong = null;
        this.muGun=builder.muGun;
    }
    private ToolBox(DengBuilder builder){
        this.bianDan = null;
        this.muTong = null;
        this.muGun=null;
    }
    public static class TiaoBuilder{
        private String bianDan;
        private String muTong;

        public TiaoBuilder setBianDan(String bianDan) {
            this.bianDan = bianDan;
            return this;
        }
        public TiaoBuilder setMuTong(String muTong) {
            this.muTong = muTong;
            return this;
        }
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    public static class TaiBuilder{
        private String muGun;
        private String muTong;

        public TaiBuilder setMuGun(String muGun) {
            this.muGun = muGun;
            return this;
        }
        public TaiBuilder setMuTong(String muTong) {
            this.muTong = muTong;
            return this;
        }
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    public static class DengBuilder{
        public ToolBox build(){
            return new ToolBox(this);
        }
    }

    //省略getter方法
}           

具體使用

ToolBox oneHeShangToolBox = new ToolBox.TiaoBuilder().setMuTong("小号木桶").setBianDan("小号扁擔").build();
ToolBox twoHeShangToolBox = new ToolBox.TaiBuilder().setMuTong("大号木桶").setMuGun("長号木棍").build();
ToolBox threeHeShangToolBox = new ToolBox.DengBuilder().build();           

建造者模式屬于建立型設計模式,它可以将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。

代理模式

為了鼓勵多勞多得,廟裡取水開始采用積分機制,每取回來一桶水就要敲一下木魚,打一次卡。這裡就可以采用代理模式。代理分為靜态代理和動态代理,為了簡單起見,這裡就用靜态代理來舉例。

public class WaterableProxy implements Waterable{
    /**
     * 被代理的原始對象
     */
    private Waterable waterable;
    
    public WaterableProxy(Waterable waterable) {
        this.waterable = waterable;
    }

    @Override
    public Water getWater() {
        Water water = waterable.getWater();
        // 增強的新功能,不管是挑水,擡水,等水,隻有帶回來水,就可以
        if(water != "無水"){
            System.out.println("我敲一下木魚,打一次卡!");
        }
        return water;
    }
}           

具體使用

public void doProxy(){
        Waterable waterable = new Taishui();
        WaterableProxy proxyWaterable = new WaterableProxy(waterable);
        proxyWaterable.getWater();
    }           

代理模式就是代理對象具備真實對象的功能,并代替真實對象完成相應操作,并能夠在操作執行的前後,對操作進行增強處理。(通過代理通路真實對象)

責任鍊模式

為了更新取水工具,将小木桶更新大金桶,寺廟決定對外提供燒香拜佛,誦經禮佛等增值服務。為了安全起見,寺廟引進了安檢機制,流程是這樣的:

•禁止攜帶寵物;

•衣着穿戴整齊;

•其它業障,最終解釋權歸寺廟;

public interface SecureFilter {
    void filter(Map context);
}

public class PetSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("寵物")){
            throw new RuntimeException("請出去:禁止攜帶寵物進入!");
        }
    }
}

public class WearSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("光膀子")){
            throw new RuntimeException("請出去:有傷風化者!");
        }
    }
}

public class OtherSecure implements SecureFilter{
    @Override
    public void filter(Map context) {
        if(context.containsKey("大聲喧嘩")){
            throw new RuntimeException("請出去:佛門乃清淨之地!");
        }
    }
}           

具體使用

/**
 * 安檢責任鍊實作
 */
class SecureChain implements SecureFilter{
    // 注入PetSecure,WearSecure,OtherSecure等過濾器
    private List<SecureFilter> secureFilterList;
    /**
     * 進入寺廟,進行安檢邏輯
     * @param context
     */
    @Override
    public void filter(Map context) {
        // 進行安檢流程
        for (SecureFilter secureFilter : secureFilterList) {
            secureFilter.filter(context);
        }
        System.out.println("佛祖保佑,安檢通過!");
    }
}           

流程示意圖

架構師日記 | 深入了解軟體設計模式



責任鍊模式一般和過濾器模式組合一起使用,即建立一個鍊條,經過這個鍊條處理的所有對象和資料分别進行依次加工,每個環節負責處理不同的業務,環節間彼此獨立解耦,同時可以複用。這種設計的巧妙之處在于可以鍊式調用,不同的過濾方式可以靈活的排序群組合。既可以使用單個過濾器進行處理,也可以直接添加一條責任鍊。

指令模式

寺廟裡的和尚除了打水工作之外,還有很多工作要做,所有的工作安排都是按照主持的指令來執行的,比如某日清晨的工作安排如下:

1.一号和尚做早餐;

2.二号和尚掃庭院;

3.三号和尚敲古鐘;

結構定義

public class Command implements Serializable {
    // 做早餐,打掃,敲鐘等指令辨別
    private OrderTypeEnum order;
    // 正向執行OR逆向復原
    private Integer direction;
    // 省略get和set方法
}

// 指令動作執行器,每種指令對應一個實作
public interface OrderHandler {
 /**
     * 執行邏輯
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext);
    /**
     * 支援的指令類型:做早餐,打掃,敲鐘等指令辨別
     *
     * @return
     */
    OrderTypeEnum getOrderType();

}

// 指令類型管理器
public interface PipelineCmd {

    /**
     * 指令行定義
     *
     * @return
     */
    Command getCommand();

    /**
     * 執行邏輯
     *
     * @param pipeContext
     * @return
     */
    PipeResult execute(PipeContext pipeContext);

    /**
     * 如果可以撤消指令,則此方法應傳回true ,否則傳回false
     *
     * @return
     */
    default boolean isReversible() {
        return true;
    }
}
 
 // 指令執行器管理器
 public interface CmdHandler {
    /**
     * 業務執行
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext);

    /**
     * 業務復原(隻復原目前指令)
     *
     * @param callContext
     * @return
     */
    PipeResult rollback(CallContext callContext);

    /**
     * 全部復原
     *
     * @param pipeContext
     * @return
     */
    PipeResult rollbackAll(PipeContext pipeContext);
}

           

指令實作

public class ZhuChiCmd implements PipelineCmd {
    private Command command;
    private transient OrderHandler orderHandler;

    public StepCmd(Command command, OrderHandler orderHandler) {
        this.command = command;
        this.orderHandler= orderHandler;
    }

    @Override
    public PipeResult execute(PipeContext pipeContext) {
        return orderHandler.execute(new CallContext(command, pipeContext));
    }
    // 省略get和set方法
}
    
    
public class Breakfast implements OrderHandler {
 /**
     * 執行邏輯
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("做早餐啦!");
    }
    /**
     * 支援的指令類型:做早餐,打掃,敲鐘等指令辨別
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.BREAKFAST;
    }

}

public class Clean implements OrderHandler {
 /**
     * 執行邏輯
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("打掃庭院啦!");
    }
    /**
     * 支援的指令類型:做早餐,打掃,敲鐘等指令辨別
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.CLEAN;
    }

}

public class Ring implements OrderHandler {
 /**
     * 執行邏輯
     *
     * @param callContext
     * @return
     */
    PipeResult execute(CallContext callContext){
        System.out.println("敲鐘啦!");
    }
    /**
     * 支援的指令類型:做早餐,打掃,敲鐘等指令辨別
     *
     * @return
     */
    OrderTypeEnum getOrderType(){
        return OrderTypeEnum.Ring;
    }

}

public class CmdFactory {
    private List<OrderHandler> orderHandlerList;

    /**
     * 擷取指定指令條件的指令對象
     *
     * @param command
     * @return
     */
     public PipelineCmd getPipelineCmd(Command command) {
        for (OrderHandler orderHandler : orderHandlerList) {
            OrderTypeEnum orderTypeEnum = orderHandler.getOrderType();
            if (orderTypeEnum.equals(command.getOrder())) {
                return new ZhuChiCmd(command, orderHandler);
            }
        }
        throw new RuntimeException("對不起主持:沒有多餘的和尚來執行新指令了!");
    }
     /**
     * 擷取給定指令的復原操作指令對象
     *
     * @param command
     * @return
     */
    public PipelineCmd getRollbackPipelineCmd(Command command) {
        Command rollbackCommand = getRollbackCommand(command);
        return getPipelineCmd(rollbackCommand);
    }
}           

具體使用

public class CmdHandlerImpl implements CmdHandler {
    private CmdFactory cmdFactory;

    @Override
    public PipeResult execute(CallContext callContext) {
        PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(callContext.getCommand());
        PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
        return pipeResult;
    }

    @Override
    public PipeResult rollback(CallContext callContext) {
        Command rollbackCommand = cmdFactory.getRollbackCommand(callContext.getCommand());
        if (rollbackCommand == null) {
            return new PipeResult("不需要復原");
        }
        PipelineCmd pipelineCmd = cmdFactory.getPipelineCmd(rollbackCommand);
        if (!pipelineCmd.isReversible()) {
            return new PipeResult("不支援復原");
        }
        PipeResult pipeResult = pipelineCmd.execute(callContext.getPipeContext());
        return pipeResult;
    }

    @Override
    public PipeResult rollbackAll(PipeContext pipeContext) {
        // 指令執行備忘錄模式對象,這裡不再展開
        Caretaker<Command> caretaker = pipeContext.getCaretaker();
        // 拿到上一步執行指令,依次循環復原
       Command command = caretaker.pop();
        while (command != null) {
            PipelineCmd pipelineCmd = cmdFactory.getRollbackPipelineCmd(command);
            if (pipelineCmd != null) {
                pipelineCmd.execute(pipeContext);
            }
            command = caretaker.pop();
        }
        return new PipeResult();
    }

}           

指令模式将一個請求封裝為一個對象,使發出的請求的對象和執行請求的對象分割開。這兩者之間通過指令對象進行溝通,這樣友善将指令對象進行儲存、傳遞、調用、增加與管理。指令模式可以與備忘錄模式組合使用,友善實作Undo和Redo操作。

3.3 實踐心得

設計原則

具體包含單一職責原則SRP、開閉原則OCP、裡氏替換原則LSP、依賴倒置原則DIP、接口隔離原則ISP、最少知識原則LKP等很多種,其核心還是圍繞着低耦合,高複用,高内聚,易擴充,易維護展開的。

模式與原則

1.設計原則是指導思想,設計模式是實作手段之一;

2.設計原則在實際開發中并不能做到完全遵守,往往是打破一些原則,遵守一些原則,來實作設計的合理性;(成本,性能)

3.設計模式往往是問題解決方案的骨架,有時候可以當做開發規範和任務拆分執行落地的技術手段;

4.一個設計模式,往往不僅僅采用一種設計原則,而是一些設計原則的整合;

5.設計模式不是一成不變的,可以根據問題場景,輸出新的模式;

6.一個複雜場景問題,有時候需要多種設計模式的組合;

7.學設計模式,死記硬背是沒用的,要從實踐中習得;

8.避免設計過度,使簡單的問題複雜化。一定要牢記簡潔原則,設計模式是為了使設計簡單,而不是更複雜;

四 總結

本文從設計模式與程式設計語言的關系,設計模式與架構模式的差別,設計原則和設計模式的關系等幾個次元進行了分析和解答。關于設計模式應該如何學習和應用的問題,給出了學習意見和實踐心得。當然,為了讓設計模式更加的直覺和立體,也花了大量篇幅在應用實踐案例上面,主要是通過場景化的案例,以設計模式的方式給出解決方案,其中部分場景為了友善了解,将問題做了簡化處理,但這不影響我們去了解設計模式要解決的問題類型。冰凍三尺非一日之寒,滴水石穿非一日之功,希望本文能夠為你帶來幫助。

作者:京東零售 劉慧卿

内容來源:京東雲開發者社群

繼續閱讀