Java設(shè)計(jì)模式之狀態(tài)模式詳解
1. 狀態(tài)模式的概述
狀態(tài)模式是一種通過將對(duì)象的狀態(tài)轉(zhuǎn)換邏輯分布到狀態(tài)對(duì)象中來實(shí)現(xiàn)狀態(tài)轉(zhuǎn)換的設(shè)計(jì)模式。它將對(duì)象的行為與對(duì)應(yīng)的狀態(tài)分離,使得在修改對(duì)象狀態(tài)時(shí),不需要修改對(duì)象的行為方法。同時(shí),狀態(tài)模式可以通過將狀態(tài)的轉(zhuǎn)換邏輯包含在各個(gè)狀態(tài)類中來簡(jiǎn)化代碼,避免出現(xiàn)大量的條件判斷語句,從而提高代碼的可讀性和可維護(hù)性。
根據(jù) GoF 的定義,狀態(tài)模式的三個(gè)核心角色分別是:
- 環(huán)境(Context):它定義了客戶端所感興趣的接口,并維護(hù)一個(gè)當(dāng)前狀態(tài),在具體狀態(tài)類中實(shí)現(xiàn)該接口的各個(gè)具體操作。
- 抽象狀態(tài)(State):它定義了一個(gè)接口,用于封裝環(huán)境對(duì)象中不同狀態(tài)對(duì)應(yīng)的行為。
- 具體狀態(tài)(Concrete State):它實(shí)現(xiàn)了抽象狀態(tài)接口,封裝了不同狀態(tài)下對(duì)環(huán)境對(duì)象的響應(yīng)行為。
2. 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)
在 Java 中,狀態(tài)模式的實(shí)現(xiàn)方法比較簡(jiǎn)單,通??梢园凑找韵虏襟E進(jìn)行:
- 定義抽象狀態(tài)接口(State),它包含了具體狀態(tài)所對(duì)應(yīng)的操作方法;
- 定義具體狀態(tài)類(ConcreteState1、ConcreteState2等),它們實(shí)現(xiàn)了抽象狀態(tài)接口,封裝了具體的狀態(tài)行為;
- 定義環(huán)境類(Context),它包含了當(dāng)前狀態(tài)以及對(duì)外提供的操作接口;
- 在環(huán)境類中,使用一個(gè)State類型的變量來表示當(dāng)前狀態(tài),并在具體操作中調(diào)用該狀態(tài)的方法;
- 當(dāng)狀態(tài)發(fā)生改變時(shí),修改環(huán)境對(duì)象的狀態(tài)即可。
下面是 Java 中狀態(tài)模式的一個(gè)簡(jiǎn)單實(shí)現(xiàn):
// 定義抽象狀態(tài)接口 interface State { void handle(); } // 定義具體狀態(tài)類 class ConcreteState1 implements State { @Override public void handle() { System.out.println("當(dāng)前狀態(tài)為 State1."); } } class ConcreteState2 implements State { @Override public void handle() { System.out.println("當(dāng)前狀態(tài)為 State2."); } } // 定義環(huán)境類 class Context { private State state; public void setState(State state) { this.state = state; } public void request() { state.handle(); } } // 示例程序 public class StatePatternDemo { public static void main(String[] args) { // 創(chuàng)建狀態(tài)對(duì)象 State state1 = new ConcreteState1(); State state2 = new ConcreteState2(); // 創(chuàng)建環(huán)境對(duì)象 Context context = new Context(); context.setState(state1); context.request(); context.setState(state2); context.request(); } }
在上述代碼中,我們首先定義了抽象狀態(tài)接口State和兩個(gè)具體狀態(tài)類ConcreteState1、ConcreteState2,它們分別實(shí)現(xiàn)了State接口。然后,我們定義了一個(gè)包含狀態(tài)切換邏輯的環(huán)境類Context,其中,使用狀態(tài)對(duì)象來表示當(dāng)前的狀態(tài),并在request方法中調(diào)用當(dāng)前狀態(tài)的handle方法。最后,我們創(chuàng)建一個(gè)示例程序,調(diào)用context的setState方法來改變狀態(tài),并觀察其輸出。
3. 狀態(tài)模式的優(yōu)缺點(diǎn)
狀態(tài)模式具有如下優(yōu)點(diǎn):
- 結(jié)構(gòu)清晰、封裝性好:將狀態(tài)的轉(zhuǎn)換邏輯分布到獨(dú)立的狀態(tài)類中,使得狀態(tài)之間的耦合度降低,并且可以將狀態(tài)的行為封裝在狀態(tài)類中,提高了系統(tǒng)的可維護(hù)性和可讀性。
- 擴(kuò)展性好:對(duì)于新的狀態(tài),只需要?jiǎng)?chuàng)建一個(gè)具體狀態(tài)類即可,同時(shí)也可以很方便地增加新的狀態(tài)轉(zhuǎn)換。
- 易于維護(hù)和調(diào)試:狀態(tài)模式將各個(gè)狀態(tài)進(jìn)行了封裝,每個(gè)狀態(tài)對(duì)象都只關(guān)注自身的行為,使得代碼易于維護(hù)和調(diào)試。
但是狀態(tài)模式也存在一些缺點(diǎn):
- 狀態(tài)模式會(huì)導(dǎo)致系統(tǒng)類和對(duì)象的個(gè)數(shù)增加:狀態(tài)模式將每個(gè)狀態(tài)都封裝成了獨(dú)立的對(duì)象,因此會(huì)增加系統(tǒng)的復(fù)雜度和實(shí)現(xiàn)難度。
- 狀態(tài)模式的使用條件較為苛刻:由于狀態(tài)模式要求將狀態(tài)轉(zhuǎn)換邏輯包含在具體狀態(tài)類中,因此只適合“狀態(tài)不多”且“狀態(tài)轉(zhuǎn)換比較少”的情況,否則會(huì)導(dǎo)致系統(tǒng)的維護(hù)和擴(kuò)展變得困難。
4. 狀態(tài)模式的適用場(chǎng)景
狀態(tài)模式通常適用于以下情形:
- 行為隨狀態(tài)改變而改變的場(chǎng)景:在狀態(tài)模式中,行為是由狀態(tài)決定的,因此當(dāng)一個(gè)對(duì)象狀態(tài)改變時(shí),它所對(duì)應(yīng)的行為也隨之改變。
- 條件、分支語句多的場(chǎng)景:如果使用傳統(tǒng)的if/else語句實(shí)現(xiàn)狀態(tài)轉(zhuǎn)換邏輯,通常會(huì)出現(xiàn)大量的條件語句,從而導(dǎo)致代碼復(fù)雜度的提高,而狀態(tài)模式可以很好地解決這個(gè)問題。
具體來說,狀態(tài)模式通常適用于以下場(chǎng)景:
- 對(duì)象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為;
- 某個(gè)操作有多個(gè)狀態(tài),且這些狀態(tài)之間可以相互轉(zhuǎn)換;
- 在不同狀態(tài)下執(zhí)行的操作有大量重復(fù)代碼時(shí),可以將該重復(fù)代碼封裝在具體狀態(tài)類中,從而提高代碼的重用性和可維護(hù)性。
5. 示例程序的設(shè)計(jì)與實(shí)現(xiàn)
下面我們將使用一個(gè)簡(jiǎn)單示例來說明狀態(tài)模式的具體實(shí)現(xiàn)方法。假設(shè)我們正在開發(fā)一個(gè)多線程下載器程序,該程序可以同時(shí)下載多個(gè)文件,并且可以監(jiān)控每個(gè)文件的下載進(jìn)度,當(dāng)某個(gè)文件下載完成后,該程序需要自動(dòng)關(guān)閉該文件的下載線程并向用戶發(fā)送下載完成的提示信息。
為了實(shí)現(xiàn)上述功能,我們可以使用狀態(tài)模式對(duì)下載器程序進(jìn)行重構(gòu),具體設(shè)計(jì)如下:
- 定義抽象狀態(tài)類,并聲明抽象的 download 方法,用于封裝不同狀態(tài)下的共性操作。
- 定義具體狀態(tài)類,并實(shí)現(xiàn) download 方法,用于完成具體的狀態(tài)操作邏輯。
- 在 ConcreteState 類中,定義一個(gè)靜態(tài)變量來表示當(dāng)前狀態(tài),在 download 方法中根據(jù)下載狀態(tài)進(jìn)行狀態(tài)轉(zhuǎn)換。
- 在 Context 類中,維護(hù)一個(gè)當(dāng)前狀態(tài),并將 download 方法委托給當(dāng)前狀態(tài)對(duì)象來執(zhí)行。
接下來我們來看一下示例程序的具體實(shí)現(xiàn)。在本示例程序中,我們使用了 Java 中的線程池和 FutureTask,實(shí)現(xiàn)了對(duì)多個(gè)文件的同時(shí)下載。需要注意的是,由于本文篇幅較長(zhǎng),為了讓代碼更加清晰,我們將代碼拆分成了多個(gè)類來實(shí)現(xiàn)相應(yīng)的功能。
(1)抽象狀態(tài)類
public abstract class DownloadState { protected DownloadContext context; public void setContext(DownloadContext context) { this.context = context; } public abstract void download(String url, String filePath); }
在上述代碼中,我們定義了一個(gè)抽象狀態(tài)類 DownloadState,它包含了一個(gè) DownloadContext 對(duì)象,以及一個(gè) download 方法,用于封裝不同狀態(tài)下的下載操作。需要注意的是,該抽象方法不包含具體的下載邏輯,具體的下載邏輯需要在具體狀態(tài)類中進(jìn)行實(shí)現(xiàn)。
(2)具體狀態(tài)類
public class DownloadingState extends DownloadState { private FutureTask<Integer> futureTask; @Override public void download(String url, String filePath) { System.out.println("開始下載文件:" + filePath); // 開始下載 DownloadTask task = new DownloadTask(url, filePath); futureTask = new FutureTask<>(task); ThreadPool.getInstance().execute(futureTask); // 狀態(tài)轉(zhuǎn)換 try { int result = futureTask.get(); if (result == 0) { context.setState(new FinishedState()); } else { context.setState(new ErrorState()); } } catch (Exception e) { e.printStackTrace(); context.setState(new ErrorState()); } } } public class FinishedState extends DownloadState { @Override public void download(String url, String filePath) { System.out.println("文件已下載完成,無需重復(fù)下載!"); context.closeDownloadThread(filePath); } } public class ErrorState extends DownloadState { @Override public void download(String url, String filePath) { System.out.println("下載文件出錯(cuò),無法繼續(xù)下載!"); context.closeDownloadThread(filePath); } }
在上述代碼中,我們定義了三個(gè)具體狀態(tài)類:DownloadingState、FinishedState 和 ErrorState,它們分別代表下載中、下載完成和下載出錯(cuò)三種狀態(tài)。其中,我們使用了線程池和 FutureTask 來實(shí)現(xiàn)下載操作,并根據(jù)下載結(jié)果進(jìn)行狀態(tài)轉(zhuǎn)換(對(duì)于下載成功的情況,我們轉(zhuǎn)換到FinishedState;對(duì)于下載出錯(cuò)的情況,我們轉(zhuǎn)換到ErrorState)。
需要注意的是,由于下載完成或下載出錯(cuò)后都需要關(guān)閉下載線程,因此我們?cè)贔inishedState和ErrorState中都調(diào)用了context對(duì)象的closeDownloadThread方法來實(shí)現(xiàn)該功能。
(3)環(huán)境類
public class DownloadContext { private DownloadState currentState; private Map<String, FutureTask<Integer>> taskMap; public DownloadContext() { this.currentState = new FinishedState(); this.taskMap = new HashMap<>(); } public void setState(DownloadState state) { this.currentState = state; this.currentState.setContext(this); } public void download(String url, String filePath) { FutureTask<Integer> task = this.taskMap.get(filePath); if (task == null || task.isDone() || task.isCancelled()) { this.taskMap.remove(filePath); this.currentState.download(url, filePath); } else { System.out.println("文件 " + filePath + " 正在下載中,無需重復(fù)下載!"); } } public void closeDownloadThread(String filePath) { FutureTask<Integer> task = this.taskMap.get(filePath); if (task != null) { task.cancel(true); this.taskMap.remove(filePath); System.out.println("已關(guān)閉文件 " + filePath + " 的下載線程。"); } } }
在上述代碼中,我們定義了一個(gè) DownloadContext 類,它包含了當(dāng)前狀態(tài)以及 download 和 closeDownloadThread 方法。
在 download 方法中,我們首先檢查是否存在正在下載的任務(wù)(即 task 對(duì)象是否存在且未完成),如果不存在,則將當(dāng)前狀態(tài)轉(zhuǎn)換為下載中狀態(tài),并啟動(dòng)下載任務(wù);否則輸出提示信息,防止重復(fù)下載。
在 closeDownloadThread 方法中,我們將傳入的 filePath 對(duì)應(yīng)的下載任務(wù)取消,并從 taskMap 中移除該任務(wù),同時(shí)輸出提示信息。
(4)線程池類
public class ThreadPool { private ExecutorService executor; private ThreadPool() { this.executor = Executors.newFixedThreadPool(5); } private static class Singleton { private static final ThreadPool INSTANCE = new ThreadPool(); } public static ThreadPool getInstance() { return Singleton.INSTANCE; } public void execute(Runnable task) { this.executor.execute(task); } }
在上述代碼中,我們定義了一個(gè) ThreadPool 類,它包含了一個(gè)靜態(tài)的 ExecutorService 對(duì)象 executor,并封裝了一個(gè) execute 方法用于提交任務(wù)到線程池中。
需要注意的是,由于我們要將ThreadPool類設(shè)計(jì)為單例模式,因此我們?cè)谠擃愔卸x了一個(gè)私有的靜態(tài)內(nèi)部類Singleton,用于實(shí)現(xiàn)懶漢式單例模式。這樣可以保證線程池中只有一個(gè)實(shí)例對(duì)象,并且線程安全。
(5)示例程序
public class Main { public static void main(String[] args) { DownloadContext context = new DownloadContext(); String url1 = "https://cdn.pixabay.com/photo/2018/10/30/16/06/water-lily-3784022__340.jpg"; String filePath1 = "water-lily.jpg"; String url2 = "https://cdn.pixabay.com/photo/2020/07/14/13/10/excursion-5407227__340.jpg"; String filePath2 = "excursion.jpg"; context.download(url1, filePath1); context.download(url2, filePath2); System.out.println("------------------------------------"); context.download(url1, filePath1); context.download(url2, filePath2); } }
在上述代碼中,我們創(chuàng)建了一個(gè) DownloadContext 對(duì)象,并分別下載了兩個(gè)文件。需要注意的是,在第二次下載同一個(gè)文件時(shí),系統(tǒng)會(huì)輸出提示信息“文件正在下載中,無需重復(fù)下載!”。
運(yùn)行該程序,我們可以看到如下輸出結(jié)果:
開始下載文件:water-lily.jpg
開始下載文件:excursion.jpg
------------------------------------
文件正在下載中,無需重復(fù)下載!
文件正在下載中,無需重復(fù)下載!
從輸出結(jié)果中我們可以看出,根據(jù)不同的狀態(tài),下載器程序完成了不同的操作,并且順利地將多線程下載操作與狀態(tài)轉(zhuǎn)換功能封裝在了不同的狀態(tài)類中。
以上就是Java設(shè)計(jì)模式之狀態(tài)模式詳解的詳細(xì)內(nèi)容,更多關(guān)于Java狀態(tài)模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java利用正則表達(dá)式提取數(shù)據(jù)的方法
最近由于項(xiàng)目需求需要提取txt里的數(shù)據(jù),之前用C#實(shí)現(xiàn)過,由于最近學(xué)習(xí)了java,所以嘗試用java實(shí)現(xiàn)下,這篇文章主要介紹了Java利用正則表達(dá)式提取數(shù)據(jù)的方法,需要的朋友可以參考下,下面來一起看看吧。2016-12-12spring cloud gateway 如何修改請(qǐng)求路徑Path
這篇文章主要介紹了spring cloud gateway 修改請(qǐng)求路徑Path的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06logback的isDebugEnabled日志配置級(jí)別源碼解析
這篇文章主要為大家介紹了logback的isDebugEnabled日志配置級(jí)別源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11springboot 實(shí)現(xiàn)mqtt物聯(lián)網(wǎng)的示例代碼
這篇文章主要介紹了springboot 實(shí)現(xiàn)mqtt物聯(lián)網(wǎng),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Java在Word中插入上標(biāo)和下標(biāo)的實(shí)現(xiàn)方法
在某些情況下,你可能需要在Microsoft?Word中插入上標(biāo)和下標(biāo)。例如,當(dāng)你正在創(chuàng)建一個(gè)涉及科學(xué)公式的學(xué)術(shù)文件時(shí),在這篇文章中,你將學(xué)習(xí)如何使用Spire.Doc?for?Java庫在Word文檔中插入上標(biāo)和下標(biāo),需要的朋友可以參考下2022-10-10