設計模式之狀態模式


狀態模式:

  狀態模式( State Pattern)也稱為狀態機模式( State Machine pattern),是允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類,屬於行為型模式。

  允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類狀態模式中類的行為是由狀態決定的,不同的狀態下有不同的行為。其意圖是讓一個對象在其內部改變的時候,其行為也隨之改變。狀態模式核心是狀態與行為綁定,不同的狀態對應不同的行為。

狀態模式的應用場景:

  比如訂單狀態的變化,審核流程單子狀態的變化等等。

  在軟件開發過程中,對於某一項操作,可能存在不同的情況。通常處理方式就是使用 if.else或 switch. case條件語句進行枚舉。但是這種做法天然存在弊端:條件判斷語句過於臃腫,可讀性差,且不具備擴展性,維護難度也大,而如果轉換思維,將這些不同狀態獨立起來用各個不同的類進行表示,系統處於那種狀態,直接使用相應的狀態類對象進行處理,消除了 if. else, switch.ase等冗余語句,代碼具有層次性且具備良好擴展力

  狀態模式主要解決的就是當控制一個對象狀態的條件表達式過於復雜時得情況,通過把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把復雜的判斷邏輯簡單化。對象得行為依賴於它的狀態(屬性),並且會根據它的狀態改變而改變它的相關行為。狀態模式適用於以下場景:

  • 行為隨狀態改變而改變的場景
  • 一個操作中含有龐大的多分支結構,並且這些分支取決於對象的狀態

狀態模式主要包含三種角色:

  1. 環境類角色( Context):定義客戶端需要的接口,內部維護一個當前狀態實例,並負責具體狀態的切換
  2. 抽象狀態角色( State):定義該狀態下的行為,可以有一個或多個行為;
  3. 具體狀態角色( Concretestate):具體實現該狀態對應的行為,並且在需要的情況下進行狀態切換

狀態模式在業務場景中的應用:

  我們在閱讀文章時,如果覺得文章寫的很好,我們就會評論、收藏兩連發。如果處於登錄情況下,我們就可以直接做評論,收藏這些行為。否則,跳轉到登錄界面,登錄后再繼續執行先前的動作。這里涉及的狀態有兩種:登錄與未登錄,行為有兩種:評論,收藏。下面我們使狀態模式來實現一下這個邏輯,代碼如下。首先創建抽象狀態角色 UserState類:

public abstract class UserState { protected AppContext context; public void setContext(AppContext context) { this.context = context; } public abstract void favorite(); public abstract void comment(String comment); }

  創建登錄狀態類:

public class LoginState extends UserState { @Override public void favorite() { System.out.println("收藏成功!"); } @Override public void comment(String comment) { System.out.println(comment); } }

  創建未登錄狀態類:

public class UnLoginState extends UserState { @Override public void favorite() { this.switch2login(); this.context.getState().favorite(); } @Override public void comment(String comment) { this.switch2login(); this.context.getState().comment(comment); } private void switch2login(){ System.out.println("跳轉到登錄頁!"); this.context.setState(this.context.STATE_LOGIN); } }

  上下文類:

public class AppContext { public static final UserState STATE_LOGIN = new LoginState(); public static final UserState STATE_UNLOGIN = new UnLoginState(); private UserState currentState = STATE_LOGIN; { STATE_LOGIN.setContext(this); STATE_UNLOGIN.setContext(this); } public void setState(UserState state){ this.currentState = state; } public UserState getState(){ return this.currentState; } public void favorite(){ this.currentState.favorite(); } public void comment(String comment){ this.currentState.comment(comment); } }

  測試:

public class Test { public static void main(String[] args) { AppContext context = new AppContext(); context.favorite(); context.comment("評論:好文章,360個贊"); } }

利用狀態機實現訂單狀態流轉控制:

  狀態機是狀態模式的一種應用,相當於上下文角色的一個升級版。在工作流或游戲等各種系統中有大量使用,如各種工作流引擎,它幾乎是狀態機的子集和實現,封裝狀態的變化規則Spring也提供紿了我們一個很好的解決方案。 Spring中的組件名稱就叫 StateMachine(狀態機)。狀態機幫助開發者簡化狀態控制的開發過程,讓狀態機結構更加層次化。下面,我們用Spring狀態機模擬一個訂單狀態流轉的過程。

添加依賴:

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

  創建訂單實體類:

public class Order { private int id; private OrderStatus status; public void setStatus(OrderStatus status) { this.status = status; } public OrderStatus getStatus() { return status; } public void setId(int id) { this.id = id; } public int getId() { return id; } @Override public String toString() { return "訂單號:" + id + ", 訂單狀態:" + status; } }

  創建訂單狀態枚舉以及狀態流轉枚舉:

public enum OrderStatus { // 待支付
 WAIT_PAYMENT, //待發貨
 WAIT_DELIVER, //待收貨
 WAIT_RECEIVE, //訂單結束
 FINISH; } //以下操作會引起狀態變化
public enum OrderStatusChangeEvent { // 支付,發貨,確認收貨
 PAYED, DELIVERY, RECEIVED; }

  添加狀態流轉配置:

@Configuration //啟用Spring StateMachine狀態機功能
@EnableStateMachine(name = "orderStateMachine") public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> { /** * 初始化當前狀態機擁有哪些狀態 * * @param states * @throws Exception */
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception { states.withStates() .initial(OrderStatus.WAIT_PAYMENT)//定義了初始狀態是WAIT_PAYMENT狀態。
                .states(EnumSet.allOf(OrderStatus.class));//定義了定義狀態機中存在的所有狀態。
 } /** * 配置狀態轉換事件關系 * 初始化當前狀態機有哪些狀態遷移動作,其中命名中我們很容易理解每一個遷移動作,都有來源狀態source,目標狀態target以及觸發事件event。 * @param transitions * @throws Exception */
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception { //狀態的轉換,待支付->支付,觸發得事件是支付
        transitions.withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED) .and() .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY) .and() .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED); } /** * 持久化配置 * 實際使用中,可以配合redis等,進行持久化操作 * * @return */ @Bean public DefaultStateMachinePersister persister() { return new DefaultStateMachinePersister<>(new StateMachinePersist<Object, Object, Order>() { @Override public void write(StateMachineContext<Object, Object> context, Order order) throws Exception { //此處並沒有進行持久化操作
 } @Override public StateMachineContext<Object, Object> read(Order order) throws Exception { //此處直接獲取order中的狀態,其實並沒有進行持久化讀取操作
                return new DefaultStateMachineContext(order.getStatus(), null, null, null); } }); } }

  添加訂單狀態監聽器:

@Component("orderStateListener") @WithStateMachine(name = "orderStateMachine") public class OrderStateListenerImpl { @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER") public boolean payTransition(Message<OrderStatusChangeEvent> message) { Order order = (Order) message.getHeaders().get("order"); order.setStatus(OrderStatus.WAIT_DELIVER); System.out.println("支付,狀態機反饋信息:" + message.getHeaders().toString()); return true; } @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE") public boolean deliverTransition(Message<OrderStatusChangeEvent> message) { Order order = (Order) message.getHeaders().get("order"); order.setStatus(OrderStatus.WAIT_RECEIVE); System.out.println("發貨,狀態機反饋信息:" + message.getHeaders().toString()); return true; } @OnTransition(source = "WAIT_RECEIVE", target = "FINISH") public boolean receiveTransition(Message<OrderStatusChangeEvent> message) { Order order = (Order) message.getHeaders().get("order"); order.setStatus(OrderStatus.FINISH); System.out.println("收貨,狀態機反饋信息:" + message.getHeaders().toString()); return true; } }

  添加訂單服務接口:

public interface IOrderService { //創建新訂單
 Order create(); //發起支付
    Order pay(int id); //訂單發貨
    Order deliver(int id); //訂單收貨
    Order receive(int id); //獲取所有訂單信息
    Map<Integer, Order> getOrders(); }

  添加實現:

@Service("orderService") public class OrderServiceImpl implements IOrderService { @Autowired private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine; @Autowired private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister; private int id = 1; private Map<Integer, Order> orders = new HashMap<>(); public Order create() { Order order = new Order(); order.setStatus(OrderStatus.WAIT_PAYMENT); order.setId(id++); orders.put(order.getId(), order); return order; } public Order pay(int id) { Order order = orders.get(id); System.out.println("線程名稱:" + Thread.currentThread().getName() + " 嘗試支付,訂單號:" + id); Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build(); if (!sendEvent(message, order)) { System.out.println("線程名稱:" + Thread.currentThread().getName() + " 支付失敗, 狀態異常,訂單號:" + id); } return orders.get(id); } public Order deliver(int id) { Order order = orders.get(id); System.out.println("線程名稱:" + Thread.currentThread().getName() + " 嘗試發貨,訂單號:" + id); if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build(), orders.get(id))) { System.out.println("線程名稱:" + Thread.currentThread().getName() + " 發貨失敗,狀態異常,訂單號:" + id); } return orders.get(id); } public Order receive(int id) { Order order = orders.get(id); System.out.println("線程名稱:" + Thread.currentThread().getName() + " 嘗試收貨,訂單號:" + id); if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build(), orders.get(id))) { System.out.println("線程名稱:" + Thread.currentThread().getName() + " 收貨失敗,狀態異常,訂單號:" + id); } return orders.get(id); } public Map<Integer, Order> getOrders() { return orders; } /** * 發送訂單狀態轉換事件 * * @param message * @param order * @return */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) { boolean result = false; try { orderStateMachine.start(); //嘗試恢復狀態機狀態
 persister.restore(orderStateMachine, order); //添加延遲用於線程安全測試
            Thread.sleep(1000); result = orderStateMachine.sendEvent(message); //持久化狀態機狀態
 persister.persist(orderStateMachine, order); } catch (Exception e) { e.printStackTrace(); } finally { orderStateMachine.stop(); } return result; } }

  測試:

@SpringBootApplication public class Test { public static void main(String[] args) { Thread.currentThread().setName("主線程"); ConfigurableApplicationContext context = SpringApplication.run(Test.class, args); IOrderService orderService = (IOrderService) context.getBean("orderService"); orderService.create();//創建訂單 orderService.create();//創建訂單 orderService.pay(1);訂單1支付 new Thread("客戶線程") { @Override public void run() { orderService.deliver(1); orderService.receive(1); } }.start(); orderService.pay(2); orderService.deliver(2); orderService.receive(2); System.out.println("全部訂單狀態:" + orderService.getOrders()); } }

狀態模式與責任鏈模式:

  狀態模式和責任鏈模式都能消除if分支過多的問題。但某些情況下,狀態模式中的狀態可以理解為責任,那么這種情況下,兩種模式都可以使用從定義來看,狀態模式強調的是一個對象內在狀態的改變,而責任鏈模式強調的是外部節點對象間的改變。從其代碼實現上來看,他們間最大的區別就是狀態模式各個狀態對象知道自己下一個要進入的狀態對象;而責任鏈模式並不清楚其下一個節點處理對象,因為鏈式組裝由客戶端負責。

狀態模式與策略模式:

  狀態模式和策略模式的UML類圖架構幾乎完全一樣, 但他們的應用場景是不一樣的。策略模式多種算法行為擇其一都能滿足,彼此之間是獨立的,用戶可自行更換策略算法;而狀態模式各個狀態間是存在相互關系的,彼此之間在一定條件下存在自動切換狀態效果,且用戶無法指定狀態,只能設置初始狀態。

優點:

  • 結構清晰:將狀態獨立為類, 消除了冗余的if...else或switch...case語句, 使代碼更加簡潔,提高系統可維護性;
  • 將狀態轉換顯示化:通常的對象內部都是使用數值類型來定義狀態,狀態的切換是通過賦值進行表現,不夠直觀;而使用狀態類,在切換狀態時,是以不同的類進行表示,轉換目的更加明確;
  • 狀態類職責明確且具備擴展性。

缺點:

  • 類膨脹:如果一個事物具備很多狀態,則會造成狀態類太多;
  • 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂;
  • 狀態模式對開閉原則的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM