定義
狀態模式(State Pattern)是一種行為型設計模式,它允許一個對象在其內部狀態改變時改變它的行為。這種模式將每個狀態的行為封裝到對應的狀態類中,使得上下文(Context)的行為隨著其內部狀態的改變而改變,看起來像是改變了其類。
結構
狀態模式通常包含以下角色:
- 上下文(Context):維護一個指向當前狀態對象的引用,并將與狀態相關的行為委托給當前狀態對象。
- 狀態(State):定義一個接口,封裝與上下文的一個特定狀態相關的行為。
- 具體狀態(Concrete States):實現狀態接口的類,每一個類封裝了上下文對象的一個特定狀態的行為。
解決的問題
- 行為隨狀態改變:
- 當一個對象的行為依賴于其內部狀態,并且需要在運行時根據狀態的改變而改變行為時,狀態模式提供了一種清晰的方式來實現這種依賴。
- 狀態邏輯分散問題:
- 在沒有使用狀態模式的情況下,對象的狀態邏輯常常分散在整個對象中,尤其是在對象的行為受多個狀態影響時。狀態模式通過將每個狀態的行為封裝在單獨的類中,使得狀態相關的邏輯集中管理。
- 復雜的條件選擇結構:
- 狀態模式幫助避免在對象的行為實現中使用復雜的條件選擇結構(如if-else或switch-case語句)。通過將行為封裝在狀態對象中,可以使用多態替代條件語句。
- 狀態轉換的顯式表示:
- 狀態模式使得狀態的轉換過程更加明確和顯式。狀態對象可以控制轉換到下一個狀態的邏輯,這樣狀態轉換規則就不再隱含在對象的行為實現中。
- 狀態增加的靈活性:
- 使用狀態模式時,新增狀態只涉及添加一個新的狀態類。這比在原有的條件邏輯中添加新的分支要清晰和簡單得多,同時也更容易維護。
使用場景
- 對象的行為取決于其狀態:
- 當一個對象的行為隨其內部狀態的改變而改變時,狀態模式是非常適用的。例如,一個文檔可能有多種狀態(如草稿、審閱、發布),每種狀態下的行為(如編輯、審批、發布)都不同。
- 復雜的條件分支控制:
- 當一個操作包含大量的條件分支,并且這些分支依賴于對象的狀態時,使用狀態模式可以避免復雜的條件分支邏輯,使得代碼更易于理解和維護。
- 狀態的轉換邏輯顯式化:
- 如果狀態轉換的邏輯很復雜,或者有多個地方會觸發狀態轉換,狀態模式可以使這些邏輯集中管理,避免分散在系統的各個部分,從而降低出錯的概率。
- 狀態機的實現:
- 對于那些可以用狀態機來描述的系統,狀態模式提供了一種清晰的實現方式。每個狀態都是狀態機的一個節點,狀態轉換則對應于狀態機的邊。
- 減少代碼重復:
- 如果在不同狀態下的行為有重復代碼,狀態模式可以幫助組織和重用這些代碼,尤其是當這些行為在狀態間有細微差別時。
示例代碼1-交通信號燈
// 狀態接口
interface TrafficLightState {void change(TrafficLight trafficLight);
}// 具體狀態:紅燈
class RedLight implements TrafficLightState {public void change(TrafficLight trafficLight) {System.out.println("Red Light - Stop");trafficLight.setState(new GreenLight());}
}// 具體狀態:綠燈
class GreenLight implements TrafficLightState {public void change(TrafficLight trafficLight) {System.out.println("Green Light - Go");trafficLight.setState(new YellowLight());}
}// 具體狀態:黃燈
class YellowLight implements TrafficLightState {public void change(TrafficLight trafficLight) {System.out.println("Yellow Light - Caution");trafficLight.setState(new RedLight());}
}// 上下文
class TrafficLight {private TrafficLightState state;public TrafficLight(TrafficLightState state) {this.state = state;}public void setState(TrafficLightState state) {this.state = state;}public void change() {state.change(this);}
}// 客戶端代碼
public class TrafficLightDemo {public static void main(String[] args) {TrafficLight trafficLight = new TrafficLight(new RedLight());trafficLight.change(); // Red Light - StoptrafficLight.change(); // Green Light - GotrafficLight.change(); // Yellow Light - Caution}
}
示例代碼2-文檔狀態管理
// 狀態接口
interface DocumentState {void next(Document doc);void prev(Document doc);void printStatus();
}// 具體狀態:草稿
class Draft implements DocumentState {public void next(Document doc) {doc.setState(new Review());}public void prev(Document doc) {System.out.println("The document is in its initial state.");}public void printStatus() {System.out.println("Document in Draft state.");}
}// 具體狀態:審閱
class Review implements DocumentState {public void next(Document doc) {doc.setState(new Published());}public void prev(Document doc) {doc.setState(new Draft());}public void printStatus() {System.out.println("Document in Review state.");}
}// 具體狀態:發布
class Published implements DocumentState {public void next(Document doc) {System.out.println("The document is already published.");}public void prev(Document doc) {doc.setState(new Review());}public void printStatus() {System.out.println("Document in Published state.");}
}// 上下文
class Document {private DocumentState state;public Document() {state = new Draft();}public void setState(DocumentState state) {this.state = state;}public void next() {state.next(this);}public void prev() {state.prev(this);}public void printStatus() {state.printStatus();}
}// 客戶端代碼
public class DocumentDemo {public static void main(String[] args) {Document document = new Document();document.printStatus(); // Document in Draft state.document.next();document.printStatus(); // Document in Review state.document.next();document.printStatus(); // Document in Published state.}
}
主要符合的設計原則
- 開閉原則(Open-Closed Principle):
- 狀態模式允許在不修改現有代碼的情況下添加新狀態。這是因為你可以添加新的狀態類來擴展系統的行為,而無需更改現有的上下文或其他狀態類。因此,系統對于擴展是開放的,但對于修改是封閉的。
- 單一職責原則(Single Responsibility Principle):
- 在狀態模式中,每個狀態類僅負責管理與其狀態相關的行為,而上下文類則負責維護狀態和委托狀態相關的任務。這樣,每個類只有一個改變的原因,即它所代表的狀態的行為,從而符合單一職責原則。
- 里氏替換原則(Liskov Substitution Principle):
- 狀態模式中的每個具體狀態都是通過狀態接口或抽象狀態類實現的,這意味著它們都可以互換使用。因此,任何時候都可以使用這些具體狀態來替代狀態接口,這符合里氏替換原則。
在JDK中的應用
- java.util.Iterator:
Iterator
接口的實現類通常會根據內部的集合狀態(如集合的當前位置)來改變其行為。例如,在遍歷過程中,hasNext()
和next()
方法的行為取決于迭代器的當前位置。
- java.net.URLConnection:
URLConnection
類及其子類有不同的狀態,如連接前和連接后。在連接打開之后,嘗試修改連接屬性(如調用setDoOutput()
方法)將拋出異常,表明其行為隨狀態變化。
- java.nio.channels.Selector:
- 在Java NIO中,
Selector
類的行為依賴于它當前的狀態(如打開或關閉狀態)。選擇器的某些操作只在打開狀態下有效,而在關閉狀態下會有不同的行為或拋出異常。
- 在Java NIO中,
在Spring中的應用
- Spring Web Flow:
- Spring Web Flow是Spring框架的一個擴展,它管理著基于狀態的流程。在這種情況下,根據當前流程的狀態(如步驟或頁面),應用程序的行為會有所不同。Spring Web Flow通過定義狀態和轉換來管理復雜的流程邏輯。
- Spring狀態機(Spring State Machine):
- Spring State Machine提供了一種系統化的方式來實現狀態模式,允許定義狀態、事件和在不同狀態下觸發的行為。雖然這是一個獨立的項目,但它與Spring框架集成,為實現基于狀態的邏輯提供了更結構化的方法。
- Spring Security的認證流程:
- 在Spring Security中,認證和授權流程可能根據不同的狀態(如已認證、未認證、授權失敗)執行不同的邏輯。例如,不同的認證提供者可以根據上下文或安全令牌的狀態來決定認證邏輯。
- Spring Session:
- 在Spring Session中,會話的狀態(如新建、已存在、過期)可以決定某些行為(如會話創建、會話獲取、會話失效)。