主頁 > 軟體設計 > 架構師日記-深入理解軟體設計模式

架構師日記-深入理解軟體設計模式

2023-05-06 08:17:37 軟體設計

作者:京東零售 煉訓卿

一 設計模式與編程語言

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.避免設計過度,使簡單的問題復雜化,一定要牢記簡潔原則,設計模式是為了使設計簡單,而不是更復雜;

四 總結

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

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/551776.html

標籤:其他

上一篇:架構師日記-深入理解軟體設計模式

下一篇:返回列表

標籤雲
其他(158535) Python(38118) JavaScript(25403) Java(18023) C(15222) 區塊鏈(8261) C#(7972) AI(7469) 爪哇(7425) MySQL(7162) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5335) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4565) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1965) Web開發(1951) HtmlCss(1932) python-3.x(1918) 弹簧靴(1913) C++(1912) xml(1889) PostgreSQL(1874) .NETCore(1857) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 架構師日記-深入理解軟體設計模式

    本文從設計模式與編程語言的關系,設計模式與架構模式的區別,設計原則和設計模式的關系等幾個維度進行了分析和解答。關于設計模式應該如何學習和應用的問題,給出了學習意見和實踐心得。 ......

    uj5u.com 2023-05-06 08:17:37 more
  • 架構師日記-深入理解軟體設計模式

    本文從設計模式與編程語言的關系,設計模式與架構模式的區別,設計原則和設計模式的關系等幾個維度進行了分析和解答。關于設計模式應該如何學習和應用的問題,給出了學習意見和實踐心得。 ......

    uj5u.com 2023-05-06 08:17:06 more
  • 中臺,真的是一場自欺欺人的騙局嗎?

    前段時間,隨著阿里集團CEO張勇的公開信發布,阿里集團也做出了歷史上最大的一次組織調整。



    隨著新的1+6+N的組織陣型的調整和落地,阿里曾經的中臺戰略,變得有點非常的尷尬了,似乎成為了一個巨大的爭議。 ......

    uj5u.com 2023-05-05 09:41:44 more
  • 抽象工廠模式(Abstract Factory Pattern)

    回顧工廠方法設計模式的不足:具體產品增加時,系統中類的個數將成對增加,在一定程度上增加了系統的復雜度 模式動機 產品等級結構:產品等級結構即產品的繼承結構,即抽象產品與具體產品 產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位于不同產品等級結構中的一組產品 模式定義 提供一個創建一系列相關 ......

    uj5u.com 2023-05-05 09:41:33 more
  • 我設計了個【方案】:比redis好10倍的kv庫【一統kv】

    基于ssd磁盤,此我設計了比redis更好的快取方案。此方案:沒有快取擊穿問題。沒有快取雪崩問題。沒有快取污染問題。沒有熱key問題。
    不需要snap和aof。支持任何sql庫,sql庫不需要帶有任何分布式功能。 ......

    uj5u.com 2023-05-05 09:41:28 more
  • 【訪問者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    簡介 訪問者模式(Visitor Pattern)是一種行為型模式。它封裝一個訪問者類,把各元素類的操作集合起來,目的是將資料結構與資料操作分離。在不改變原有元素類資料結構的前提下,改變了元素類的執行演算法。 當某些較為穩定的東西(資料結構或演算法),不想直接被改變但又想擴展功能,這時候適合用訪問者模式 ......

    uj5u.com 2023-05-05 09:41:21 more
  • 抽象工廠模式(Abstract Factory Pattern)

    回顧工廠方法設計模式的不足:具體產品增加時,系統中類的個數將成對增加,在一定程度上增加了系統的復雜度 模式動機 產品等級結構:產品等級結構即產品的繼承結構,即抽象產品與具體產品 產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位于不同產品等級結構中的一組產品 模式定義 提供一個創建一系列相關 ......

    uj5u.com 2023-05-05 09:41:04 more
  • 【訪問者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    簡介 訪問者模式(Visitor Pattern)是一種行為型模式。它封裝一個訪問者類,把各元素類的操作集合起來,目的是將資料結構與資料操作分離。在不改變原有元素類資料結構的前提下,改變了元素類的執行演算法。 當某些較為穩定的東西(資料結構或演算法),不想直接被改變但又想擴展功能,這時候適合用訪問者模式 ......

    uj5u.com 2023-05-05 09:40:57 more
  • 中臺,真的是一場自欺欺人的騙局嗎?

    前段時間,隨著阿里集團CEO張勇的公開信發布,阿里集團也做出了歷史上最大的一次組織調整。



    隨著新的1+6+N的組織陣型的調整和落地,阿里曾經的中臺戰略,變得有點非常的尷尬了,似乎成為了一個巨大的爭議。 ......

    uj5u.com 2023-05-05 09:40:30 more
  • 我設計了個【方案】:比redis好10倍的kv庫【一統kv】

    基于ssd磁盤,此我設計了比redis更好的快取方案。此方案:沒有快取擊穿問題。沒有快取雪崩問題。沒有快取污染問題。沒有熱key問題。
    不需要snap和aof。支持任何sql庫,sql庫不需要帶有任何分布式功能。 ......

    uj5u.com 2023-05-05 09:40:21 more