架構師內功心法,參與電商訂單業務開發的狀態模式詳解

狀態模式在生活場景中也是比較常見的。好比咱們平時網購的訂單狀態變化,還有平時坐電梯,電梯狀態的變化。 前端

在軟件開發過程當中,對於某一項的操做,可能存在不一樣的狀況。一般處理多狀況問題最直接的辦法就是使用if...else或者switch...case條件語句進行判斷。這種作法對於複雜狀態的判斷自然存在弊端:判斷條件語句過於臃腫,可讀性較差,不具有擴展性,維度難度也很大。若是轉換一下思惟,將這些不一樣狀態獨立起來用各類不一樣的類進行表示,系統處理哪一種狀況,直接使用相應的狀態類進行處理,消除條件判斷語句,代碼也更加具備層次感,且具有良好的擴展能力。redis

狀態模式(State Pattern)也成爲狀態機模式(State Machine Pattern),是容許對象在內部狀態發生改變時改變它的行爲,對象看起來好像修改了它的類。狀態模式中類的行爲是由狀態決定的,不一樣的狀態下有不一樣的行爲。其意圖是讓一個對象在其內部改變的時候,其行爲也隨之改變。狀態模式的核心就是狀態與行爲綁定,不一樣的狀態對應不一樣的行爲。算法

1、狀態模式的應用場景

狀態模式適用於如下幾種場景:spring

  • 行爲隨狀態改變而改變場景;
  • 一個操做中含有龐大的多分支機構,而且這些分支取決於對象的狀態。

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

  • 環境類角色(Context):定義客戶端須要的接囗,內部維護一個當前狀態實例,並負責具體狀態的切換;
  • 抽象狀態角色(State):定義該狀態下的行爲,能夠有一個或多個行爲;
  • 具體狀態角色(ConcreteState):具體實現該狀態對應的行爲而且在須要的肩況下進行狀態切換。

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

咱們在某社區閱讀文章的時候,若是以爲某篇文章寫得好,就會轉發、收藏而且評論。若是用戶處於登陸狀況下,咱們就能夠作評論、轉發、收藏這些行爲。不然須要跳轉到登陸頁面,登陸以後才能執行先前的動做。那麼這裏涉及到的狀態有兩種:已登陸和未登陸,行爲有三種:評論、轉發、收藏。下面使用代碼來實現這些邏輯,首先建立抽象狀態角色類UserState:前端框架

public abstract class UserState {

    private AppContext appContext;

    public void setAppContext(AppContext appContext) {
        this.appContext = appContext;
    }

    public abstract void forward();

    public abstract void collect();

    public abstract void comment(String comment);

}

建立登陸狀態LoginState類:架構

public class LoginState extends UserState {

    @Override
    public void forward() {
        System.out.println("轉發成功!");
    }

    @Override
    public void collect() {
        System.out.println("收藏成功!");
    }

    @Override
    public void comment(String comment) {
        System.out.println("評論成功,內容是:" + comment);
    }
}

接着建立未登陸狀態UnLoginState類:app

public class UnLoginState extends UserState {

    @Override
    public void forward() {
        forward2Login();
        this.appContext.forward();
    }

    @Override
    public void collect() {
        forward2Login();
        this.appContext.collect();
    }

    @Override
    public void comment(String comment) {
        forward2Login();
        this.appContext.comment(comment);
    }

    private void forward2Login() {
        System.out.println("跳轉到登陸頁面!");
        this.appContext.setState(this.appContext.LOGIN_STATE);
    }
}

建立上下文角色AppContext類:框架

public class AppContext {

    public static final UserState LOGIN_STATE = new LoginState();

    public static final UserState UNLOGIN_STATE = new UnLoginState();

    private UserState currentState = UNLOGIN_STATE;

    {
        UNLOGIN_STATE.setAppContext(this);
        LOGIN_STATE.setAppContext(this);
    }

    public void setState(UserState state) {
        this.currentState = state;
        this.currentState.setAppContext(this);
    }

    public UserState getState() {
        return this.currentState;
    }

    public void forward() {
        this.currentState.forward();
    }

    public void collect() {
        this.currentState.collect();
    }

    public void comment(String comment) {
        this.currentState.comment(comment);
    }

}

測試main方法:ide

public static void main(String[] args) {
    AppContext context = new AppContext();
    context.forward();
    context.collect();
    context.comment("說的太好了,雙手雙腳給個贊👍");
}

運行結果:

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

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

一、pom依賴

<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
@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)
                .states(EnumSet.allOf(OrderStatus.class));
    }
 
    /**
     * 配置狀態轉換事件關係
     * @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;
    }
}

六、建立IOrderService接口

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

七、在Service中添加業務邏輯

@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;
    }
}

測試main方法:

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);

    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());

}

2、狀態模式中的源碼體現

狀態模式的具體應用在源碼中很是少見,在源碼中通常只是提供一種通用的解決方案。若是必定要找,固然也是能找到的。經歷千辛萬苦,持續燒腦,下面咱們來看一個在JSF源碼中的Lifecycle類。JSF也算是一個比較經典的前端框架,那麼沒用過的小夥伴也不要緊,咱們這是隻是分析一下其設計思想。在JSF中它全部頁面的處理分爲6個階段,被定義在了Phaseld類中用不一樣的常量來表示生命週期階段,源碼以下:

public class PhaseId implements Comparable {
    private final int ordinal;
    private String phaseName;
    private static int nextOrdinal = 0;
    private static final String ANY_PHASE_NAME = "ANY";
    public static final PhaseId ANY_PHASE = new PhaseId("ANY");
    private static final String RESTORE_VIEW_NAME = "RESTORE_VIEW";
    public static final PhaseId RESTORE_VIEW = new PhaseId("RESTORE_VIEW");
    private static final String APPLY_REQUEST_VALUES_NAME = "APPLY_REQUEST_VALUES";
    public static final PhaseId APPLY_REQUEST_VALUES = new PhaseId("APPLY_REQUEST_VALUES");
    private static final String PROCESS_VALIDATIONS_NAME = "PROCESS_VALIDATIONS";
    public static final PhaseId PROCESS_VALIDATIONS = new PhaseId("PROCESS_VALIDATIONS");
    private static final String UPDATE_MODEL_VALUES_NAME = "UPDATE_MODEL_VALUES";
    public static final PhaseId UPDATE_MODEL_VALUES = new PhaseId("UPDATE_MODEL_VALUES");
    private static final String INVOKE_APPLICATION_NAME = "INVOKE_APPLICATION";
    public static final PhaseId INVOKE_APPLICATION = new PhaseId("INVOKE_APPLICATION");
    private static final String RENDER_RESPONSE_NAME = "RENDER_RESPONSE";
    public static final PhaseId RENDER_RESPONSE = new PhaseId("RENDER_RESPONSE");
    private static final PhaseId[] values;
    public static final List VALUES;

    private PhaseId(String newPhaseName) {
        this.ordinal = nextOrdinal++;
        this.phaseName = null;
        this.phaseName = newPhaseName;
    }

    public int compareTo(Object other) {
        return this.ordinal - ((PhaseId)other).ordinal;
    }

    public int getOrdinal() {
        return this.ordinal;
    }

    public String toString() {
        return null == this.phaseName ? String.valueOf(this.ordinal) : this.phaseName + ' ' + this.ordinal;
    }

    static {
        values = new PhaseId[]{ANY_PHASE, RESTORE_VIEW, APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, INVOKE_APPLICATION, RENDER_RESPONSE};
        VALUES = Collections.unmodifiableList(Arrays.asList(values));
    }
}

那麼這些狀態的切換都在Lifecycle的execute()方、去中進行。其中會傳一個參數FacesContext對象,最終全部的狀態都被FacesContext保存。在此呢,咱們就不作繼續深刻的分析。

3、狀態模式的相關模式

3.1 狀態模式與責任鏈模式

狀態模式和責任鏈模式都能消除if分支過多的問題。但某些狀況下,狀態模式中的狀態能夠理解爲責任,那麼這種狀況下,兩種模式均可以使用。

從定義來看,狀態模式強調的是一個對象內在狀態的改變,而責任鏈模式強調的是外部節點對象間的改變。

從其代碼實現上來看,他們間最大的區別就是狀態模式各個狀態對象知道本身下一個要進入的狀態對象而責任鏈模式並不清楚其下一個節點處理對象,由於鏈式組裝由客戶端負責。

3.2 狀態模式與策略模式

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

4、狀態模式的優缺點

優勢:

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

缺點:

  • 類膨脹:若是一個事物具有不少狀態,則會形成狀態類太多;
  • 狀態模式的結構與實現都較爲複雜,若是使用不當將致使程序結構和代碼的混亂;
  • 狀態模式對開閉原則的支持並不太好,對於能夠切換狀態的狀態模式增長新的狀態類須要修改那些負責狀態轉換的源代碼,不然沒法切換到新增狀態,並且修改某個狀態類的行爲也需修改對應類的源代碼。
相關文章
相關標籤/搜索