設計模式之策略模式和狀態模式(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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...