設計模式之策略模式和狀態模式(strategy pattern & state pattern)

来源:https://www.cnblogs.com/yssjun/archive/2019/07/03/11116652.html
-Advertisement-
Play Games

本文來講解一下兩個結構比較相似的行為設計模式:策略模式和狀態模式。兩者單獨的理解和學習都是比較直觀簡單的,但是實際使用的時候卻並不好實踐,算是易學難用的設計模式吧。這也是把兩者放在一起介紹的原因,經過對比和實例介紹,相信應該會一些比較深刻的感知。最後在結合個人的體會簡單聊一下對這兩個模式的一些看法。 ...


本文來講解一下兩個結構比較相似的行為設計模式:策略模式和狀態模式。兩者單獨的理解和學習都是比較直觀簡單的,但是實際使用的時候卻並不好實踐,算是易學難用的設計模式吧。這也是把兩者放在一起介紹的原因,經過對比和實例介紹,相信應該會一些比較深刻的感知。最後在結合個人的體會簡單聊一下對這兩個模式的一些看法。

1. 模式概念

1.1 策略模式

運行時更改類的行為或演算法,從而達到修改其功能的目的;

使用場景: 一個系統需要動態地在幾種演算法中選擇一種,而這些演算法之間僅僅是他們的行為不同。 此外決策過程中過多的出現if else,也可以考慮使用該模式。

實現:將這些演算法封裝成可單獨運行的類,由使用者根據需要進行替換。

優點: 較為靈活,擴展性好,避免大量的if else結構。

缺點: 對外暴露了類所有的行為和演算法,行為過多導致策略類膨脹。

1.2 狀態模式

運行時類的行為由其狀態決定;

使用場景: 對象依賴裝填,行為隨狀態改變而改變的情景,或者存在大量的if else和分支結構等;

實現:將對象的狀態封裝成單個的類,每個狀態處理該狀態下的事務,並控制該狀態到其他狀態的轉移;

優點: 容易新加狀態,封裝了狀態轉移規則,每個狀態可以被覆用和共用,避免大量的if else結構。

缺點: 該模式結構和實現相對複雜,狀態過多導致增加類和對象個數。同時由於由每個狀態控制向其他狀態的轉移,新加狀態必須要修改現有的部分狀態才能加入狀態機中生效。

1.3 相同點

兩者通過將行為和狀態拆分成一系列小的組件,由條件和狀態進行功能更替,這樣符合開閉原則,便於擴展。此外均可作為if else或者分支的替換方案;支持的最大行為和狀態均有限;

1.4 不同點

  • 策略模式中,類的功能是根據當前條件主動更改;
  • 狀態模式中,類的功能是被動由當前狀態更改;
  • 策略模式中每個行為或演算法之間沒有關聯;
  • 狀態模式中的狀態之間有關聯,並且狀態本身控制著狀態轉移;

2. 原理

兩種模式的結構非常相似,下麵分別看一下兩種設計模式的UML類圖:

2.1 策略模式UML

描述:

Context:

使用了某種策略的類,其行為由其包含的具體的策略決定,該類能主動修改使用的策略從而改變其行為;

Strategy:

抽象策略類,用於定義所有支持的演算法公共介面;

ConcreteStrategy:

能夠被Context使用的具體的策略;

2.2 狀態模式UML

描述:

Context:

帶有某個狀態標記的類,其行為由其當前的狀態決定,類狀態的轉移由狀態來控制;

State:

抽象狀態類,用於定義Context所有狀態的公共介面;

ConcreteState:

Context類的某種具體狀態,包含了該狀態下處理的事務並控制向他狀態轉移;

3. 實例——策略模式

舉個壓縮軟體使用不同壓縮策略的例子。

 

抽象策略介面:Compression

public interface Compression {
    public void doCompression();
}

快速壓縮演算法:Rapid

public class Rapid implements Compression {
    @Override
    public void doCompression() {
        System.out.println("Use rapid compression strategy!");
    }
}

高效壓縮演算法:Efficient

public class Efficient implements Compression {
    @Override
    public void doCompression() {
        System.out.println("Use efficient compression strategy!");
    }
}

加密壓縮演算法:Encrypt

public class Encrypt implements Compression {
    @Override
    public void doCompression() {
        // TODO Auto-generated method stub
        System.out.println("Use encrypt compression strategy!");
    }
}

集成上面壓縮演算法的軟體:WinRAR

public class WinRAR {
    
    private Compression compression = null;
    
    public WinRAR(Compression compression) {
        this.compression = compression;
    }
    
    public void setStrategy(Compression compression) {
        this.compression = compression;
    }
    
    public void compression() {
        if (compression != null) {
            compression.doCompression();
        }
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        WinRAR winrar = new WinRAR(new Rapid());
        winrar.compression();
        winrar.setStrategy(new Efficient());
        winrar.compression();
        winrar.setStrategy(new Encrypt());
        winrar.compression();
    }
}

結果:

Use rapid compression strategy!
Use efficient compression strategy!
Use encrypt compression strategy!

這個例子看著很直觀,後面會給出一點分析和個人的理解。

4. 實例——狀態模式

我們通過自動洗衣機工作過程來描述一下狀態模式使用。

簡單起見,這裡我們僅僅考慮【開始】-> 【工作】-> 【結束】,這三個狀態。

下麵先來看一下其UML的類圖:

抽象狀態介面:State

public interface State {
    public void doJob(Washing washing);
}

開始狀態:Start

public class Start implements State {
    @Override
    public void doJob(Washing washing) {
        System.out.println("Start Washing Clothes!");
        washing.setState(new Work());
        washing.request();
    }
}

工作狀態:Work

public class Work implements State{
    @Override
    public void doJob(Washing washing) {
        System.out.println("Working Now!");
        washing.setState(new End());
        washing.request();
    }
}

結束狀態:End

public class End implements State{
    @Override
    public void doJob(Washing washing) {
        System.out.println("All Finished!");
        washing.setState(null);
    }
}

洗衣機類:Washing

public class Washing {
    private State state = null;
    
    public void setState(State state) {
        this.state = state;
        if (state == null) {
            System.out.println("Current state: null!");
        }
        else {
            System.out.println("Current state: " + state.getClass().getName());
        }
    }
    
    public void request() {
        if (state != null) {
            state.doJob(this);
        }
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Washing washing = new Washing();
        washing.setState(new Start());
        washing.request();
    }
}

結果:

Current state: state.Start
Start Washing Clothes!
Current state: state.Work
Working Now!
Current state: state.End
All Finished!
Current state: null!

washing中提供用戶使用的主要介面。初始時,使用者使用一個狀態來配置washing,然後便可對washing發送指令,後續不在需要用戶直接於具體轉態打交道。每個狀態會自動控制向下一個狀態轉移,直到運行結束。

5. 總結

談一下個人對於策略設計模式和狀態模式的一些理解(不一定對,僅僅是一些思考,歡迎討論):

5.1 策略模式:

a)頻繁使用if else 可能嚴重消耗性能

策略模式比較適用於,行為類經常在某一個模式下工作,而不是會根據隨機條件進行切換。

舉個例子,在APP開發過程中,某一功能會依賴於橫豎屏狀態,那麼我們是否需要在每一幀都是使用if else進行判斷當前是橫屏還是豎屏,然後進行下一步的處理?

顯然這會嚴重消耗性能,正確的做法是將橫豎屏處理拆分成兩個策略,每次屏幕切換的時候,主動的切一下使用的模式;

b) 並不是所有的if else 和 分支都可以使用策略模式來替代

對於上面的壓縮軟體的例子,用戶會選用一種模式,然後進行下麵的工作,這個沒問題。

但是如果我們提供的是一個壓縮命令,該命令可以根據傳遞的參數,使用不同的壓縮方式,那麼使用if else就是必要的,因為我們不知道用戶會輸入什麼參數,使用什麼模式。

c) 策略模式沒有策略

策略模式的核心是將一系列的操作拆分成可若幹可單獨重覆使用的輪子,特定條件下直接選取其中一個使用,而不是傳遞條件,使用if else來進行條件判斷以執行相應的操作。

從這個角度來看,策略模式名不副實,其不僅沒有智能,合理的根據當前條件進行決策,還需要使用者主動的選取一種策略進行執行。這樣做有好處,但同時其也變得更加沒有策略。

實際開發過程中,我們都希望對方提供的介面簡單好用,最好一個介面能搞定所有的問題,因為對於調用者而言,我並不關心你的實現,我只關心簡單使用這個介面完成我的需求。

根本原因在於其破壞了封裝性,暴露了具體的策略,這是其拆分組件便於擴展的同時帶來的一個不可迴避的問題,策略模式將決策由執行者提前到了調用者,代碼靈活可擴展的同時帶來的是使用的不便。

如果說策略模式主要是為了避免大量的if else決策,那麼語言支持的話完全可以使用hashtable,分別以條件和函數對象作為key,value來直接根據條件選取對應的操作。對於大量分支尤其適用。

因此實際開發過程中需要根據自己的實際情況權衡利弊。

5.2 狀態模式:

狀態模式的核心是將對象每一個狀態做的事情分別交給每一個單獨的狀態對象處理,並且由狀態自己控制向其他狀態的轉移;行為類僅向外提供方便用戶使用的介面;

對擴展狀態不是特別友好,需要修改其他狀態的轉移。其次其實現比較靈活,用不好容易出錯。

小結:

策略模式是通過 Context 本身的決策來主動更替使用的strategy對象達到改變行為的目的,狀態模式通過狀態轉移來被動的更改當前的State對象,狀態的改變發生在運行時。

策略模式提前封裝一組可以互相替代的演算法族,根據需要動態的選擇合適的一個來處理問題,而狀態模式處理不同狀態下, Context 對象行為不同的問題;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 從輸入URL到渲染出整個頁面的過程包括三個部分: 1、DNS解析URL的過程 2、瀏覽器發送請求與伺服器交互的過程 3、瀏覽器對接收到的html頁面渲染的過程 一、DNS解析URL的過程 DNS解析的過程就是尋找哪個伺服器上有請求的資源。因為ip地址不容易記憶,一般會使用URL功能變數名稱(如www.bai ...
  • 上篇簡單介紹了入口方法的流程以及scanner類相關的部分內容,這一篇主要講scanner的初始化,即 註意,這不是調用靜態方法。實際上Parser實例生成的時候也把scanner屬性初始化了,所以這裡可以直接用。 實際上,就是初始化了scanner上的source_屬性與模塊的flag,以便調用I ...
  • runTest("b* 1", function() { b * 1; }); 綜上比較, 1、本身是數字的字元串轉為數字,parseInt()不帶參數直接轉最快; 2、字元串既包含數字又包含字母的字元串,parseInt()帶10進位的參數更快,但是是所有方法中最慢的; 3、如果是純數字組成的字元 ...
  • 什麼是鏈表? 鏈表和數組的對比:在大多數語言中,數組的大小是固定的,從數組的起點或中間添加或刪除元素的成本很高,因為需要移動元素。 鏈表中的每一個元素在記憶體中不是連續放置的,和它左右兩側元素是沒有關係的。 每個元素有一個存儲元素本身的節點和指向下一個元素的引用組成。 相對於數組,鏈表的好處在於添加或 ...
  • 1.如果上一頁是靜態頁面,可以用 history.go(-1)方法; go() 方法可載入歷史列表中的某個具體的頁面。 該參數可以是數字,使用的是要訪問的 URL 在 History 的 URL 列表中的相對位置。(-1上一個頁面,1前進一個頁面)。或一個字元串,字元串必須是局部或完整的URL,該函 ...
  • 最近在做項目時,碰到 safari 瀏覽器不支持location跳轉問題,針對此問題,可以通過 js 模擬點擊時間來解決,代碼如下: ...
  • 最近使用layui的框架時,發現table插件不支持鍵盤快捷鍵切換單元格,花了點時間實現此功能。 分享給有需要的朋友們~~~ 效果圖 代碼: 1.支持 enter,上,下,右鍵 切換單元格,支持隱藏列跳過切換。註:單元格必須開啟了 edit:text 模式,才支持鍵盤切換。使用方法:1.在需要啟用此 ...
  • 跟我學SpringCloud | 第四篇:熔斷器Hystrix 1. 熔斷器 服務雪崩 在正常的微服務架構體系下,一個業務很少有隻需要調用一個服務就可以返回數據的情況,這種比較常見的是出現在demo中,一般都是存在調用鏈的,比如A B C D,如果D在某一個瞬間出現問題,比如網路波動,io偏高,導致 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...