深入理解Java設(shè)計(jì)模式之狀態(tài)模式
一、什么是狀態(tài)模式
定義:當(dāng)一個(gè)對象的內(nèi)在狀態(tài)改變時(shí)允許改變其行為,這個(gè)對象看起來像是改變了其類。
主要解決:當(dāng)控制一個(gè)對象狀態(tài)的條件表達(dá)式過于復(fù)雜時(shí)的情況。把狀態(tài)的判斷邏輯轉(zhuǎn)移到表示不同狀態(tài)的一系列類中,可以把復(fù)雜的判斷邏輯簡化。
意圖:允許一個(gè)對象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為
二、狀態(tài)模式的結(jié)構(gòu)
在該類圖中,我們看到三個(gè)角色:
(1)Context
: 環(huán)境類,定義客戶感興趣的接口,維護(hù)一個(gè)State子類的實(shí)例,這個(gè)實(shí)例對應(yīng)的是對象當(dāng)前的狀態(tài)。
(2)State
:抽象狀態(tài)類或者狀態(tài)接口,定義一個(gè)或者一組行為接口,表示該狀態(tài)下的行為動(dòng)作。
(3)ConcreteState
: 具體狀態(tài)類,實(shí)現(xiàn)State抽象類中定義的接口方法,從而達(dá)到不同狀態(tài)下的不同行為。
三、狀態(tài)模式的使用場景
1.一個(gè)對象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為。
2.一個(gè)操作中含有龐大的多分支結(jié)構(gòu),并且這些分支決定于對象的狀態(tài)。
例子:我們在微博上看到一篇文章,覺得還不錯(cuò),于是想評論或者轉(zhuǎn)發(fā),但如果用戶沒有登錄,這個(gè)時(shí)候就會(huì)先自動(dòng)跳轉(zhuǎn)到登錄注冊界面,如果已經(jīng)登錄,當(dāng)然就可以直接評論或者轉(zhuǎn)發(fā)了。這里我們可以看到,我們用戶的行為是由當(dāng)前是否登錄這個(gè)狀態(tài)來決定的,這就是典型的狀態(tài)模式情景。
當(dāng)然還包括很多其他動(dòng)作,例如轉(zhuǎn)發(fā)、分享、打賞等等,都要重復(fù)判斷狀態(tài)才行,如果程序隨著需求的改動(dòng)或者功能邏輯的增加需要修改代碼,那么你只要遺漏了一個(gè)判斷,就會(huì)出問題。
而使用狀態(tài)模式,可以很好地避免過多的if–else –分支,狀態(tài)模式將每一個(gè)狀態(tài)分支放入一個(gè)獨(dú)立的類中,每一個(gè)狀態(tài)對象都可以獨(dú)立存在,程序根據(jù)不同的狀態(tài)使用不同的狀態(tài)對象來實(shí)現(xiàn)功能。
四、狀態(tài)模式和策略模式對比
如果我們在編寫代碼的時(shí)候,遇到大量的條件判斷的時(shí)候,可能會(huì)采用策略模式來優(yōu)化結(jié)構(gòu),因?yàn)檫@時(shí)涉及到策略的選擇,但有時(shí)候仔細(xì)查看下,就會(huì)發(fā)現(xiàn),這些所謂的策略其實(shí)是對象的不同狀態(tài),更加明顯的是,對象的某種狀態(tài)也成為判斷的條件。
策略模式的Context含有一個(gè)Strategy的引用,將自身的功能委托給Strategy來完成。
我們把Strategy接口改個(gè)名字為State,這就是狀態(tài)模式了,同樣Context也有一個(gè)State類型的引用,也將自己的部門功能委托給State來完成。
要使用狀態(tài)模式,我們必須明確兩個(gè)東西:狀態(tài)和每個(gè)狀態(tài)下執(zhí)行的動(dòng)作。
在狀態(tài)模式中,因?yàn)樗械臓顟B(tài)都要執(zhí)行相應(yīng)的動(dòng)作,所以我們可以考慮將狀態(tài)抽象出來。
狀態(tài)的抽象一般有兩種形式:接口和抽象類。如果所有的狀態(tài)都有共同的數(shù)據(jù)域,可以使用抽象類,但如果只是單純的執(zhí)行動(dòng)作,就可以使用接口。
他們之間真正的區(qū)別在策略模式對Strategy的具體實(shí)現(xiàn)類有絕對的控制權(quán),即Context要感知Strategy具體類型。而狀態(tài)模式,Context不感知State的具體實(shí)現(xiàn),Context只需調(diào)用自己的方法,這個(gè)調(diào)用的方法會(huì)委托給State來完成,State會(huì)在相應(yīng)的方法調(diào)用時(shí),自動(dòng)為Context設(shè)置狀態(tài),而這個(gè)過程對Context來說是透明的,不被感知的。
五、狀態(tài)模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1、封裝了轉(zhuǎn)換規(guī)則。
2、枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類。
3、將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對象狀態(tài)即可改變對象的行為。
4、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體,而不是某一個(gè)巨大的條件語句塊。
5、可以讓多個(gè)環(huán)境對象共享一個(gè)狀態(tài)對象,從而減少系統(tǒng)中對象的個(gè)數(shù)。
缺點(diǎn):
1、狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對象的個(gè)數(shù)。
2、狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。
3、狀態(tài)模式對“開閉原則”的支持并不太好,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài);而且修改某個(gè)狀態(tài)類的行為也需修改對應(yīng)類的源代碼。
六、狀態(tài)模式的實(shí)現(xiàn)
抽象狀態(tài)類,定義一個(gè)接口以封裝與Context類(Work)的一個(gè)特定狀態(tài)相關(guān)的行為。
public abstract class State { public abstract void WriteProgram(Work w); }
工作狀態(tài)類,每一個(gè)子類實(shí)現(xiàn)一個(gè)與Context類(Work)的一個(gè)狀態(tài)相關(guān)的行為。
//上午工作狀態(tài) public class ForeNoonState : State { public override void WriteProgram(Work w) { if (w.Hour < 12) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),上午工作,精神百倍", w.Hour); } else { //超過12點(diǎn),則轉(zhuǎn)入中午工作狀態(tài) w.SetState(new NoonState()); w.WriteProgram(); } } }
//中午工作狀態(tài) public class NoonState : State { public override void WriteProgram(Work w) { if (w.Hour < 13) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),吃午飯,午休", w.Hour); } else { //超過13點(diǎn),則轉(zhuǎn)入下午工作狀態(tài) w.SetState(new AfterNoonState()); w.WriteProgram(); } } }
//下午工作狀態(tài) public class AfterNoonState : State { public override void WriteProgram(Work w) { if (w.Hour < 17) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),下午工作,繼續(xù)努力", w.Hour); } else { //超過17點(diǎn),則轉(zhuǎn)入晚上工作狀態(tài) w.SetState(new EveningState()); w.WriteProgram(); } } }
//晚上工作狀態(tài) public class EveningState : State { public override void WriteProgram(Work w) { if (w.Finish) { //如果狀態(tài)已完成,則轉(zhuǎn)入下班狀態(tài) w.SetState(new RestState()); w.WriteProgram(); } else { if (w.Hour < 21) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),加班", w.Hour); } else { //超過21點(diǎn),則轉(zhuǎn)入睡眠工作狀態(tài) w.SetState(new SleepingState()); w.WriteProgram(); } } } }
//睡眠工作狀態(tài) public class SleepingState : State { public override void WriteProgram(Work w) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),睡覺", w.Hour); } } //下班休息狀態(tài) public class RestState : State { public override void WriteProgram(Work w) { Console.WriteLine("當(dāng)前時(shí)間:{0}點(diǎn),下班", w.Hour); } }
工作類,Context類,維護(hù)一個(gè)ConcreteState子類(工作狀態(tài)類)的實(shí)例,這個(gè)實(shí)例定義當(dāng)前的狀態(tài)
public class Work { private State current; public Work() { //工作初始化為上午工作狀態(tài) current = new ForeNoonState(); } //“鐘點(diǎn)”屬性,狀態(tài)轉(zhuǎn)換的依據(jù) private double hour; public double Hour { get { return hour; } set { hour = value; } } //“任務(wù)完成”屬性,是否能下班的依據(jù) private bool finish = false; public bool Finish { get { return finish; } set { finish = value; } } public void SetState(State s) { current = s; } public void WriteProgram() { current.WriteProgram(this); } }
客戶端代碼
class Program { //客戶端代碼 static void Main(string[] args) { Work w = new Work(); w.Hour = 9; w.WriteProgram(); w.Hour = 10; w.WriteProgram(); w.Hour = 12; w.WriteProgram(); w.Hour = 13; w.WriteProgram(); w.Hour = 14; w.WriteProgram(); w.Hour = 17; w.Finish = false; //w.Finish = true; w.WriteProgram(); w.Hour = 19; w.WriteProgram(); w.Hour = 22; w.WriteProgram(); Console.Read(); } }
結(jié)果
當(dāng)前時(shí)間:9點(diǎn),上午工作,精神百倍
當(dāng)前時(shí)間:10點(diǎn),上午工作,精神百倍
當(dāng)前時(shí)間:12點(diǎn),吃午飯,午休
當(dāng)前時(shí)間:13點(diǎn),下午工作,繼續(xù)努力
當(dāng)前時(shí)間:14點(diǎn),下午工作,繼續(xù)努力
當(dāng)前時(shí)間:17點(diǎn),加班
當(dāng)前時(shí)間:19點(diǎn),加班
當(dāng)前時(shí)間:22點(diǎn),睡覺
七、總結(jié)
在對象的行動(dòng)取決于本身的狀態(tài)時(shí),可以適用于狀態(tài)模式,免去了過多的if–else判斷,這對于一些復(fù)雜的和繁瑣的判斷邏輯有很好的幫助。但是使用狀態(tài)模式,勢必會(huì)造成更多的接口和類,對于非常簡單的狀態(tài)判斷,可以不使用。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot優(yōu)雅接收前端請求參數(shù)的詳細(xì)過程
這篇文章主要介紹了SpringBoot如何優(yōu)雅接收前端請求參數(shù),我們可以通過@RequestParm注解去綁定請求中的參數(shù),將(查詢參數(shù)或者form表單數(shù)據(jù))綁定到controller的方法參數(shù)中,本文結(jié)合示例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2023-06-06淺談Mybatis中resultType為hashmap的情況
這篇文章主要介紹了淺談Mybatis中resultType為hashmap的情況,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Java中過濾器、監(jiān)聽器和攔截器的區(qū)別詳解
這篇文章主要介紹了Java中過濾器、監(jiān)聽器和攔截器的區(qū)別詳解,有些朋友可能不了解過濾器、監(jiān)聽器和攔截器的區(qū)別,本文就來詳細(xì)講一下,相信看完你會(huì)有所收獲,需要的朋友可以參考下2024-01-01Java?InputStream實(shí)戰(zhàn)之輕松讀取操作文件流
在Java中,輸入輸出是非常重要的基礎(chǔ)功能,其中,InputStream是Java中的一個(gè)重要輸入流類,用于從輸入源讀取數(shù)據(jù),下面我們就來學(xué)習(xí)一下InputStream類的相關(guān)知識(shí)吧2023-10-10最全JVM調(diào)優(yōu)步驟和參數(shù)及配置
這篇文章主要給大家介紹了關(guān)于JVM調(diào)優(yōu)的相關(guān)資料,JVM調(diào)優(yōu)是指對Java虛擬機(jī)(JVM)進(jìn)行優(yōu)化,以提高Java程序的性能和運(yùn)行效率,文中介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03springboot如何使用@ConfigurationProperties封裝配置文件
springboot如何使用@ConfigurationProperties封裝配置文件的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08