設計模式實戰-狀態模式(State Pattern)
1 前言
有時一個對象的行為取決于一或多個動態變化的屬性(狀態),這樣的對象稱為有狀態(stateful)對象,其對象狀態是從事先定義好的一系列值中取出。當這樣的對象與外部事件產生互動時,內部狀態就會改變,對象行為也隨之變化。
在UML中可使用狀態圖描述對象狀態的變化。在狀態模式中,創建表示各種狀態的對象和一個行為隨著狀態對象改變而改變的 context 對象。
2 定義
該模式下,類的行為基于其狀態而改變。即允許一個對象在其內部狀態改變時,改變它的行為,對象看起來似乎修改了它的類。其別名為狀態對象(Objects for States),狀態模式是一種對象行為型模式。
3 架構
3.1 Context-環境類
擁有狀態的對象,環境類有時可以充當狀態管理器(State Manager),在環境類中對狀態進行切換操作。
3.2 State-抽象狀態類
可以是抽象類,也可是接口,不同狀態類就是繼承這個父類的不同子類,狀態類的產生是由于環境類存在多個狀態,同時還滿足:這些狀態經常需要切換,在不同狀態下對象行為不同。
3.3 ConcreteState-具體狀態類
角色:狀態服務方,狀態客戶方,特定狀態服務方。
通過特定狀態服務方,將原來屬于狀態服務方的多狀態模式,轉化為單狀態模式,降低了狀態服務的邏輯復雜度,提高狀態服務的可擴展性。
4 意義
解決:對象的行為依賴于它的狀態(屬性),并且可以根據它的狀態改變而改變它的相關行為。
狀態模式的關鍵是引入了一個抽象類來專門表示對象的狀態 - 抽象狀態類。而對象的每種具體狀態類都繼承該類,并在不同具體狀態類中實現不同狀態的行為,包括各種狀態之間的轉換。
將不同對象下的行為單獨提取出來,封裝在具體的狀態類,使得環境類對象在其內部狀態改變時可以改變它的行為,對象看起來似乎修改了它的類,而實際上是由于切換到具體狀態類實現。
由于環境類可設置為任一具體狀態類,因此它針對抽象狀態類進行編程,在程序運行時可以將任一具體狀態類的對象設置到環境類中,從而使得環境類可以改變內部狀態,并且改變行為。
5 優點
封裝了轉換規則
枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類
將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
6 缺點
必增加系統類和對象的個數
結構與實現都較為復雜,若使用不當將導致程序結構和代碼的混亂
對"開閉原則"支持不太好,對可切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源代碼
在DDD中,為所有狀態創建單獨的類會使系統變得復雜。對于實體狀態類來說,有些行為來自于自身,有些行為繼承自抽象基類,這一方面在子類和父類之間形成緊耦合,另一方面使代碼的可讀性變差。但是,使用枚舉則非常簡單,與通過狀態模式來創建標準類型相比,枚舉可能是更好的方法。這里我們同時得到了兩種方法的好處:
獲得了一個非常簡單的標準類型
又能有效地表示當前的狀態
7 適用場景
代碼中包含大量與對象狀態有關的條件語句:
對象的行為依賴于它的狀態(屬性)并且可以根據它的狀態改變而改變它的相關行為
代碼中包含大量與對象狀態有關的條件語句,這些條件語句的出現,會導致代碼的可維護性和靈活性變差,不能方便地增加和刪除狀態,使客戶類與類庫之間的耦合增強。在這些條件語句中包含了對象的行為,而且這些條件對應于對象的各種狀態
如何解決
將各種具體的狀態類抽象出來。
關鍵代碼
通常命令模式的接口中只有一個方法。而狀態模式的接口中有一個或者多個方法。而且,狀態模式的實現類的方法,一般返回值,或者是改變實例變量的值。也就是說,狀態模式一般和對象的狀態有關。實現類的方法有不同的功能,覆蓋接口中的方法。狀態模式和命令模式一樣,也可以用于消除 if…else 等條件選擇語句。
在行為受狀態約束的時候使用狀態模式,而且狀態不超過 5 個。
8 業務應用
在工作流或游戲等類型的軟件中得以廣泛使用,甚至可以用于這些系統的核心功能設計,如在政府OA辦公系統中,一個批文的狀態有多種:尚未辦理;正在辦理;正在批示;正在審核;已經完成等各種狀態,而且批文狀態不同時對批文的操作也有所差異。使用狀態模式可以描述工作流對象(如批文)的狀態轉換以及不同狀態下它所具有的行為。
9 案例
9.1 案例一
State 接口
實現 State 接口的實體狀態類
Context:帶有某狀態的類
StatePatternDemo,測試類使用Context 和狀態對象展示在狀態改變時的行為變化
9.2 電商訂單狀態流轉改造
電商平臺購物基本的流程:
搜索商品
進入商品列表頁面,點擊商品
進入商詳頁,了解商品之后,將商品加入購物車
進入購物車頁面,確認購買的商品之后,點擊結算按鈕
進入結算頁面,選擇收貨地址,配送方式等,點擊支付按鈕
進入支付頁面,進行訂單支付,支付完成
進入自己的訂單列表頁,可以看到自己剛剛下的訂單。
提交一個訂單后,訂單在電商系統后臺,會經歷很多狀態流轉,直至到訂單完結狀態(妥投,取消,刪除等)。訂單狀態的流轉,涉及電商系統交易,倉儲,配送等環節。不同品類商品的訂單,訂單狀態流轉差異也較大,比如虛擬商品,就不涉及倉儲,配送等環節,狀態流轉簡化不少。
訂單的每個狀態,前端支持用戶行為不同:
未支付:支付,取消,刪除
已支付:催單
已發貨:催單
已妥投:點評,刪除
已刪除:用戶就看不到了,所以不支持任何操作
訂單的狀態流轉,受商品和業務影響,復雜多變,簡化訂單特定狀態下的業務處理邏輯,提高系統對狀態的高擴展性,是狀態模式的核心意義。
功能限制,我們的重點是講解狀態模式,所以對實現的功能模塊需要進行一定的簡化。我們只關注以上五個訂單狀態(未支付,已支付,已發貨,已妥投,已刪除)和三個訂單相關的操作(支付,催單,刪除)。
每個方法都要考慮訂單各個狀態,業務邏輯雜糅,若有一個狀態需調整,這些方法基本都受影響,若要增加一個新訂單狀態,上面方法都要相應修改,擴展性差,嚴重違背開閉原則:比如 pay 接口實現,很復雜,支付功能需要考慮所有狀態 case 該如何處理,而每個狀態實際處理過程還大不相同。
狀態服務類、抽象類、狀態具體服務類
10 擴展
共享狀態
在有些情況下多個環境對象需共享同一狀態,若期望在系統中實現多個環境對象實例共享一個或多個狀態對象,那么需要將這些狀態對象定義為環境的靜態成員對象。
10.1 簡單狀態模式
狀態都相互獨立,狀態之間無須進行轉換的狀態模式,這是最簡單的一種狀態模式。
每個狀態類都封裝與狀態相關的操作,無需關心狀態切換,可在客戶端直接實例化狀態類,然后將狀態對象設置到環境類。
遵循“開閉原則”,在客戶端可以針對抽象狀態類進行編程,而將具體狀態類寫到配置文件中,同時增加新的狀態類對原有系統也不造成任何影響。
10.2 可切換狀態的狀態模式
大多數的狀態模式都是可切換狀態的狀態模式,在實現狀態切換時,在具體狀態類內部需要調用環境類Context的setState()方法進行狀態的轉換操作,在具體狀態類中可以調用到環境類的方法,因此狀態類與環境類之間通常還存在關聯關系或者依賴關系。通過在狀態類中引用環境類的對象來回調環境類的setState()方法實現狀態的切換。在這種可以切換狀態的狀態模式中,增加新的狀態類可能需要修改其他某些狀態類甚至環境類的源代碼,否則系統無法切換到新增狀態。
11 總結
狀態模式允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。其別名為狀態對象,狀態模式是一種對象行為型模式。
狀態模式包含三個角色:環境類又稱為上下文類,它是擁有狀態的對象,在環境類中維護一個抽象狀態類State的實例,這個實例定義當前狀態,在具體實現時,它是一個State子類的對象,可以定義初始狀態;抽象狀態類用于定義一個接口以封裝與環境類的一個特定狀態相關的行為;具體狀態類是抽象狀態類的子類,每一個子類實現一個與環境類的一個狀態相關的行為,每一個具體狀態類對應環境的一個具體狀態,不同的具體狀態類其行為有所不同。
狀態模式描述了對象狀態的變化以及對象如何在每一種狀態下表現出不同的行為。
狀態模式的主要優點在于封裝了轉換規則,并枚舉可能狀態,它將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為,還可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數;其缺點在于使用狀態模式會增加系統類和對象的個數,且狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂,對于可以切換狀態的狀態模式不滿足“開閉原則”的要求。
適用場景
對象的行為依賴于它的狀態(屬性)并且可以根據它的狀態改變而改變它的相關行為
代碼中包含大量與對象狀態有關的條件語句,這些條件語句的出現,會導致代碼的可維護性和靈活性變差,不能方便地增加和刪除狀態,使客戶類與類庫之間的耦合增強。
參考
https://www.bilibili.com/read/cv16247154
自建電商
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。