【設計模式】狀態模式 (狀態對象(Objects for States))

狀態模式(State Pattern)詳解


一、狀態模式簡介

狀態模式(State Pattern) 是一種 行為型設計模式(對象行為型模式),它允許一個對象在其內部狀態改變時改變其行為。換句話說,對象看起來好像修改了它的類。

你可以這樣理解:

“同一個對象,在不同狀態下表現出不同的行為。”

這就像一個自動售貨機:

  • 當你沒有投幣時,它不響應“出貨”按鈕;
  • 投幣后,它才允許你選擇商品;
  • 選擇商品后,它才會出貨并找零。

這個機器的行為隨著內部狀態(如“未投幣”、“已投幣”、“已選擇”)的變化而變化。

  • 紅綠燈
    在這里插入圖片描述

  • H2O的三種狀態(未考慮臨界點)

在這里插入圖片描述

在軟件系統中
有些對象具有多種狀態。
這些狀態在某些情況下能夠相互轉換。
對象在不同的狀態下將具有不同的行為。
復雜的條件判斷語句來進行狀態的判斷和轉換操作 --> 導致代碼的可維護性和靈活性下降 --> 出現新的狀態時,代碼的擴展性很差,客戶端代碼也需要進行相應的修改,違背了開閉原則。

// 非常多的if else
class TestXYZ 
{int behaviour;//Getter and Setter......public void HandleAll(){if (behaviour == 0){ //do something }else if (behaviour == 1){ //do something }else if (behaviour == 2){ //do something }else if (behaviour == 3){ //do something }... some more else if ...}
}

又名狀態對象(Objects for States)
用于解決系統中復雜對象的狀態轉換以及不同狀態下行為的封裝問題。
將一個對象的狀態從該對象中分離出來,封裝到專門的狀態類中,使得對象狀態可以靈活變化。
對于客戶端而言,無須關心對象狀態的轉換以及對象所處的當前狀態,無論對于何種狀態的對象,客戶端都可以一致處理。

狀態模式包含以下3個角色
Context(環境類)
State(抽象狀態類)
ConcreteState(具體狀態類)

在這里插入圖片描述


二、解決的問題類型

狀態模式主要用于解決以下問題:

  • 對象的行為依賴于其狀態,并且狀態較多導致代碼中出現大量 if-elseswitch-case 判斷邏輯
  • 希望將每種狀態的行為封裝到獨立的類中,提高可讀性和可維護性
  • 需要在運行時動態切換對象的行為

三、使用場景

場景示例
工作流/審批系統訂單狀態:待支付 → 已支付 → 發貨中 → 已完成
游戲角色控制角色狀態:站立、奔跑、跳躍、死亡等
用戶登錄狀態未登錄、已登錄、鎖定、超時等
電梯控制系統運行、停止、開門、關門等狀態

在有些情況下,多個環境對象可能需要共享同一個狀態。
如果希望在系統中實現多個環境對象共享一個或多個狀態對象,那么需要將這些狀態對象定義為環境類的靜態成員對象。

對于客戶端而言,無須關心狀態類,可以為環境類設置默認的狀態類,將狀態的轉換工作交給環境類(或具體狀態類)來完成,具體的轉換細節對于客戶端而言是透明的。
可以通過環境類來實現狀態轉換,環境類作為一個狀態管理器,統一實現各種狀態之間的轉換操作。

對象的行為依賴于它的狀態(例如某些屬性值),狀態的改變將導致行為的變化
在代碼中包含大量與對象狀態有關的條件語句,這些條件語句的出現會導致代碼的可維護性和靈活性變差,不能方便地增加和刪除狀態,并且導致客戶類與類庫之間的耦合增強。


四、核心概念

  1. Context(上下文):持有當前狀態對象的引用,對外提供接口。
  2. State(狀態接口):定義所有具體狀態類必須實現的行為。
  3. ConcreteState(具體狀態類):實現狀態接口,封裝特定狀態下的行為。

五、實際代碼案例(Java)

我們以一個簡單的“訂單”系統為例,演示狀態模式的使用。

1. 定義狀態接口

// 訂單狀態接口
interface OrderState {void pay(OrderContext context);void ship(OrderContext context);void deliver(OrderContext context);void cancel(OrderContext context);
}

2. 創建具體狀態類

// 待支付狀態
class PendingState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("訂單已支付,準備發貨...");context.setState(new ShippedState()); // 轉換到已發貨狀態}@Overridepublic void ship(OrderContext context) {System.out.println("請先支付!");}@Overridepublic void deliver(OrderContext context) {System.out.println("請先支付并發貨!");}@Overridepublic void cancel(OrderContext context) {System.out.println("訂單已取消。");context.setState(new CancelledState());}
}// 已發貨狀態
class ShippedState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("訂單已支付過,無需重復支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("訂單已經在運輸途中。");}@Overridepublic void deliver(OrderContext context) {System.out.println("貨物已送達!");context.setState(new DeliveredState());}@Overridepublic void cancel(OrderContext context) {System.out.println("發貨后無法取消訂單。");}
}// 已送達狀態
class DeliveredState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("訂單已完成,無需支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("訂單已送達,無需發貨。");}@Overridepublic void deliver(OrderContext context) {System.out.println("訂單已送達,請勿重復操作。");}@Overridepublic void cancel(OrderContext context) {System.out.println("訂單已完成,無法取消。");}
}// 已取消狀態
class CancelledState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("訂單已取消,無法支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("訂單已取消,無法發貨。");}@Overridepublic void deliver(OrderContext context) {System.out.println("訂單已取消,無法送達。");}@Overridepublic void cancel(OrderContext context) {System.out.println("訂單已取消。");}
}

3. 創建上下文類(訂單)

// 訂單上下文
class OrderContext {private OrderState state;public OrderContext() {this.state = new PendingState(); // 初始狀態:待支付}public void setState(OrderState state) {this.state = state;}// 委托給當前狀態對象處理public void pay() {state.pay(this);}public void ship() {state.ship(this);}public void deliver() {state.deliver(this);}public void cancel() {state.cancel(this);}
}

4. 客戶端測試類

public class Client {public static void main(String[] args) {OrderContext order = new OrderContext();System.out.println("=== 模擬訂單流程 ===");order.pay();     // 支付order.ship();    // 發貨(此時無實際發貨動作,僅提示)order.deliver(); // 送達order.cancel();  // 嘗試取消System.out.println("\n=== 嘗試取消未支付訂單 ===");OrderContext order2 = new OrderContext();order2.cancel();}
}

輸出結果:

=== 模擬訂單流程 ===
訂單已支付,準備發貨...
訂單已經在運輸途中。
貨物已送達!
訂單已完成,無法取消。=== 嘗試取消未支付訂單 ===
訂單已取消。

典型代碼案例:

典型的抽象狀態類代碼

abstract class State
{//聲明抽象業務方法,不同的具體狀態類可以有不同的實現public abstract void Handle();
}

典型的環境類代碼

class Context
{private State state; //維持一個對抽象狀態對象的引用private int value; //其他屬性值,該屬性值的變化可能會導致對象狀態發生變化//設置狀態對象public void SetState(State state) 
{this.state = state;}public void Request() 
{//其他代碼state.Handle(); //調用狀態對象的業務方法//其他代碼}
}

狀態轉換的實現

  1. 統一由環境類來負責狀態之間的轉換,環境類充當了狀態管理器(State Manager)角色。
     ……
public void ChangeState()
{//判斷屬性值,根據屬性值進行狀態轉換
if (value == 0)
{this.SetState(new ConcreteStateA());}else if (value == 1)
{this.SetState(new ConcreteStateB());}......}……
  1. 由具體狀態類來負責狀態之間的轉換,可以在具體狀態類的業務方法中判斷環境類的某些屬性值,再根據情況為環境類設置新的狀態對象,實現狀態轉換。
 ……
public void ChangeState(Context ctx) 
{//根據環境對象中的屬性值進行狀態轉換
if (ctx.Value == 1) 
{ctx.SetState(new ConcreteStateB());}else if (ctx.Value == 2) 
{ctx.SetState(new ConcreteStateC());}......}……

其他案例:

  1. 某軟件公司要為一銀行開發一套信用卡業務系統,銀行賬戶(Account)是該系統的核心類之一,通過分析,該軟件公司開發人員發現在系統中賬戶存在3種狀態,且在不同狀態下賬戶存在不同的行為,具體說明如下:
    (1) 如果賬戶中余額大于等于0,則賬戶的狀態為正常狀態(Normal State),此時用戶既可以向該賬戶存款也可以從該賬戶取款;
    (2) 如果賬戶中余額小于0,并且大于-2000,則賬戶的狀態為透支狀態(Overdraft State),此時用戶既可以向該賬戶存款也可以從該賬戶取款,但需要按天計算利息;
    (3) 如果賬戶中余額等于-2000,那么賬戶的狀態為受限狀態(Restricted State),此時用戶只能向該賬戶存款,不能再從中取款,同時也將按天計算利息;
    (4) 根據余額的不同,以上3種狀態可發生相互轉換。
    現使用狀態模式設計并實現銀行賬戶狀態的轉換。

在這里插入圖片描述

在這里插入圖片描述

  1. 論壇用戶等級
    在某論壇系統中,用戶可以發表留言,發表留言將增加積分;用戶也可以回復留言,回復留言也將增加積分;用戶還可以下載文件,下載文件將扣除積分。該系統用戶分為三個等級,分別是新手、高手和專家,這三個等級對應三種不同的狀態,這三種狀態分別定義如下:
    (1) 如果積分小于100分,則為新手狀態,用戶可以發表留言、回復留言,但是不能下載文件。如果積分大于等于1000分,則轉換為專家狀態;如果積分大于等于100分,則轉換為高手狀態。
    (2) 如果積分大于等于100分但小于1000分,則為高手狀態,用戶可以發表留言、回復留言,還可以下載文件,而且用戶在發表留言時可以獲取雙倍積分。如果積分小于100分,則轉換為新手狀態;如果積分大于等于1000分,則轉換為專家狀態;如果下載文件后積分小于0,則不能下載該文件。
    (3) 如果積分大于等于1000分,則為專家狀態,用戶可以發表留言、回復留言和下載文件,用戶除了在發表留言時可以獲取雙倍積分外,下載文件只扣除所需積分的一半。如果積分小于100分,則轉換為新手狀態;如果積分小于1000分,但大于等于100,則轉換為高手狀態;如果下載文件后積分小于0,則不能下載該文件。

在這里插入圖片描述

  1. 某系統要求兩個開關對象要么都處于開的狀態,要么都處于關的狀態,在使用時它們的狀態必須保持一致,開關可以由開轉換到關,也可以由關轉換到開。
    試使用狀態模式來實現開關的設計。

在這里插入圖片描述

  1. 現要開發一個屏幕放大鏡工具,其具體功能描述如下:
    用戶單擊“放大鏡”按鈕之后屏幕將放大一倍,再單擊一次“放大鏡”按鈕屏幕再放大一倍,第三次單擊該按鈕后屏幕將還原到默認大小。
    試使用狀態模式來設計該屏幕放大鏡工具。

在這里插入圖片描述


六、優缺點分析

優點描述
? 消除復雜的條件判斷語句if-elseswitch 分散到各個狀態類中,結構清晰。允許狀態轉換邏輯與狀態對象合成一體,而不是提供一個巨大的條件語句塊,可以避免使用龐大的條件語句來將業務方法和狀態轉換代碼交織在一起
? 符合開閉原則新增狀態只需添加新類,無需修改現有代碼
? 職責分離每個狀態類只關注自己狀態下的行為,職責明確
其他可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。封裝了狀態的轉換規則,可以對狀態轉換代碼進行集中管理,而不是分散在一個個業務方法中。
缺點描述
? 增加類的數量每個狀態對應一個類,可能導致類膨脹
? 狀態轉換邏輯分散狀態之間的流轉由具體狀態類控制,可能不夠集中。對開閉原則的支持并不太好。增加新的狀態類需要修改負責狀態轉換的源代碼,否則無法轉換到新增狀態;而且修改某個狀態類的行為也需要修改對應類的源代碼
? 不適合狀態較少的場景如果狀態只有兩三種,使用狀態模式反而顯得過度設計。結構與實現都較為復雜,如果使用不當將導致程序結構和代碼混亂,增加系統設計的難度

七、與策略模式對比(補充)

對比點狀態模式策略模式
目的改變對象的行為以反映其狀態變化封裝算法,使算法可互換
狀態流轉狀態之間可以相互轉換策略之間通常是獨立的
上下文關系上下文行為隨狀態變化而變上下文使用策略完成某項任務

八、最終小結

狀態模式是一種非常實用的設計模式,特別適用于那些具有多個狀態、且每個狀態下行為差異較大的對象。它通過將每種狀態封裝成獨立的類,使程序結構更清晰、易于擴展和維護。

在開發訂單系統、工作流引擎、游戲狀態管理、UI 控件狀態控制等項目中,狀態模式能有效提升代碼的可讀性和可維護性。


📌 一句話總結:

狀態模式就像一個“狀態驅動的行為控制器”,讓對象根據自己的“心情”(狀態)做出不同的反應。


? 推薦使用場景:

  • 對象有多個狀態,且狀態轉換頻繁;
  • 使用大量 if-elseswitch-case 判斷狀態;
  • 需要后期擴展更多狀態。

注意,以上部分內容由AI大模型生成,注意識別!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91087.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91087.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91087.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

工業前端組件庫重構心法:如何讓開發效率提升60%的交互模塊設計邏輯

工業前端組件庫重構心法:如何讓開發效率提升60%的交互模塊設計邏輯內容摘要在工業項目開發中,前端組件庫是提升開發效率的關鍵。然而,許多團隊的組件庫存在設計不合理、維護困難等問題,導致開發效率低下。如果能夠重構組件庫&…

leetcode 74. 搜索二維矩陣

二分查找經典題目;根據矩陣的特點,不需要把矩陣拉成一維,二維轉成一維映射關系為a[i]matrix[?i//n?][i%n];然后開始二分查找,一直二分的收縮區間;class Solution:def searchMatrix(self, matrix: List[Li…

26考研英語詞匯的邏輯筆記(Unit31-43)

行為UNIT 31詞匯數量:274 詞群數量:16 詞群邏輯:行為舉止 | 行為標準與原則 給予、收回 | 接受、允許、讓步、拒絕 促進、鼓勵 | 支持、幫助、資助 破壞相關 | 錯誤、改正 阻礙、打擾相關 | 禁止、阻止、限制 值得、有利、不利相關 | 有意、故…

Lua(數據庫訪問)

Lua 數據庫訪問方法Lua 本身不提供內置的數據庫訪問功能,但可以通過第三方庫實現與多種數據庫的交互。以下是常見的 Lua 數據庫訪問方法:使用 LuaSQL 庫LuaSQL 是一個輕量級數據庫訪問庫,支持多種數據庫后端(MySQL、PostgreSQL、S…

在 Dell PowerEdge T440 上通過 iDRAC9 安裝 Proxmox VE

在 Dell PowerEdge T440 上通過 iDRAC9 安裝 Proxmox VE 文章目錄 在 Dell PowerEdge T440 上通過 iDRAC9 安裝 Proxmox VE 1. 前置要求 1.1. 硬件信息(例) 1.2. 準備工作 2. 安裝步驟 2.1. 登錄 iDRAC9 2.2. 啟動虛擬控制臺 2.3. 掛載 Proxmox VE ISO 2.4. 設置服務器從虛擬…

window下MySQL安裝(三)卸載mysql

window下MySQL安裝&#xff08;三&#xff09;卸載mysql 卸載mysql數據庫&#xff0c;停止服務&#xff0c;備份文件&#xff0c;刪除mysql文件。結束。 停止mysql服務 以管理員身份打開命令提示符或 PowerShell&#xff1a; net stop <服務名稱> 示例&#xff1a;net st…

Elasticsearch 深度分頁問題與 `search_after` 解決方案

1. 引言 主題&#xff1a;介紹 Elasticsearch 深度分頁問題的背景&#xff0c;強調其在處理大規模數據集時的性能瓶頸。核心問題&#xff1a;傳統 from/size 分頁方式在深層分頁&#xff08;例如第500頁&#xff09;時&#xff0c;因需要加載和丟棄大量文檔&#xff0c;導致內存…

Spring Boot 2整合MyBatis Plus詳細指南

1. 環境準備Spring Boot版本&#xff1a;2.x&#xff08;推薦2.7.x&#xff09;MyBatis Plus版本&#xff1a;3.5.x&#xff08;兼容Spring Boot 2&#xff09;數據庫&#xff1a;MySQL 8.0&#xff08;其他數據庫需調整驅動&#xff09;2. 創建項目并添加依賴在pom.xml中添加核…

Docker鏡像導入解析:docker import vs docker load

本文通過Busybox鏡像的實戰演示&#xff0c;深入剖析兩個易混淆命令的技術原理與適用場景一、核心區別速覽特性docker importdocker load輸入來源容器文件系統快照(docker export輸出)完整鏡像歸檔(docker save輸出)保留信息僅文件內容完整鏡像(層/歷史/配置/標簽)生成鏡像結構…

Android 解決鍵盤遮擋輸入框

本文目錄 點擊直達Android 解決鍵盤遮擋輸入框代碼實現使用注意最后我還有一句話要說梧桐葉上三更雨&#xff0c;葉葉聲聲是別離。Android 解決鍵盤遮擋輸入框 在安卓中通常可以通過添加android:windowSoftInputMode"adjustResize|stateHidden"的方式來讓鍵盤頂起布…

熱門JavaScript庫“is“等軟件包遭npm供應鏈攻擊植入后門

輕量級 JavaScript 實用工具庫 "is" 是 NPM 平臺上的熱門項目&#xff0c;每周下載量超過 220 萬次。然而在 2025 年 7 月 19 日&#xff0c;該庫開發者遭遇釣魚攻擊導致賬戶憑證泄露&#xff0c;攻擊者借此發布了包含遠程代碼執行后門的惡意版本。釣魚攻擊入侵開發者…

如何實現緩存音頻功能(App端詳解)

本方案提供了符合平臺規范的音頻緩存實現&#xff0c;通過分層設計確保功能可靠性&#xff0c;同時預留擴展點支持未來優化需求。&#x1f4e6; 音頻緩存與播放流程說明 項目目標 實現App端音頻資源的自動緩存與播放&#xff1a; 獲取遠程音頻URL下載文件至本地_downloads/目錄…

從零到一:Django圖書管理系統完整開發實戰指南

作者&#xff1a;笙囧同學 身份&#xff1a;中科院計算機大模型方向碩士 | 全棧開發愛好者 座右銘&#xff1a;偷懶是人生進步的階梯 聯系方式&#xff1a;3251736703qq.com 可接課設&#xff0c;論文&#xff0c;專利輔導 全平臺賬號&#xff1a;笙囧同學 &#x1f4d6; 前言 …

硬件電路設計(基本元器件)

一、電阻 1.電阻的基本概念 電阻是導體對電流阻礙作用的物理量&#xff0c;符號為R&#xff0c;單位為歐姆&#xff08;Ω&#xff09;。電阻的大小由導體的材料、長度、橫截面積和溫度共同決定。 電阻公式 導體的電阻R可通過以下公式計算&#xff1a; ρ&#xff1a;電阻率&…

R語言常用擴展包

R語言的強大之處很大程度上源于其豐富的擴展包&#xff08;Packages&#xff09;生態系統&#xff0c;目前CRAN&#xff08; Comprehensive R Archive Network&#xff09;上托管的擴展包已超過20,000個。以下是不同領域最常用的核心擴展包分類介紹&#xff1a; 一、數據處理與…

文件相關問題(AI回答)

一個進程打開一個文件&#xff0c;另一個進程能刪除這個文件嗎DeepSeek-R1 回答完成深度思考完成 用時10秒是的&#xff0c;?另一個進程可以刪除一個已被打開的文件?&#xff0c;但具體行為取決于操作系統和文件系統。以下是關鍵細節&#xff1a;1. ?Linux/Unix 的行為??刪…

三相四橋臂SVPWM控制及電機模型

三相四橋臂SVPWM控制策略及其在電機驅動中應用 一、三相四橋臂逆變器拓撲特性 1. 結構優勢 中性點控制&#xff1a;第四橋臂獨立調節中性點電壓&#xff0c;支持不平衡負載電壓利用率&#xff1a;相比傳統三橋臂提升15.47%&#xff0c;最大線電壓達U_{dc}硬件簡化&#xff1a;無…

deepseek+飛書多維表格 打造小紅書矩陣

通過AI技術平臺DeepSeek的數據分析與內容生成能力&#xff0c;結合飛書多維表格的智能化協作管理&#xff0c;實現小紅書矩陣賬號的高效運營。DeepSeek精準抓取熱點趨勢并生成爆款文案&#xff0c;飛書多維表格則提供可視化內容排期、多賬號數據看板及團隊任務分配功能&#xf…

4、如何生成分布式ID?

目錄 1、分布式ID介紹 什么是 ID&#xff1f; 什么是分布式 ID&#xff1f; 分布式 ID 需要滿足哪些要求? 2、分布式 ID 常見解決方案 1、數據庫 示例使用2&#xff1a; 2、數據庫號段模式 使用示例2&#xff1a; 一、核心設計思路 二、實現代碼 1. 數據庫表設計&…

Rust 實戰三 | HTTP 服務開發及 Web 框架推薦

往期回顧 Rust 實戰二 | 開發簡易版命令行工具 grepRust 實戰一 | 用 RustRover 開發猜數字游戲Rust 安裝與版本更新 代碼開源地址&#xff1a;https://github.com/0604hx/rust-journey &#x1f680; Web 框架 名稱性能&#xff08;QPS&#xff09;WebSocket / SSEGitHub ?…