(給ImportNew加星標,提高Java技能)
來自:唐尤華
https://dzone.com/articles/modern-state-pattern-using-enums-and-functional-in
通常應根據物件的狀態改變物件的行為。只要沒有提交訂單,都可以向 ShoppingBasket(購物籃)物件新增商品。一旦訂單提交,通常不允許改變訂單內容。因此,這樣 ShoppingBasket 物件有兩種狀態:READONLY 和 UPDATEABLE。下麵是 ShoppingBasket 類定義。
// 採用 state 樣式前,訂單總是可以更新
public class ShoppingBasket {
private String orderNo;
private List articleNumbers = new ArrayList<>();
public void add(String articleNumber) {
articleNumbers.add(articleNumber);
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public void order() {
// 實現訂單邏輯,如果下單成功,應改變訂單狀態
}
}
上面的程式碼不但可以新增商品,還可以下單。提交訂單後,客戶端仍然可以修改訂單物件。這應該是不允許的。為了防止客戶端更新已經提交的訂單,應當改變 ShoppingBasket 的行為。一旦提交訂單,就不能新增商品或更改 orderNo。上面這樣的問題,用 Java 面向物件語言如何解決?這種情況下,我通常用列舉實現 GoF state 樣式,示例如下:
public enum UpdateState {
UPDATEABLE(()->Validate.validState(true)), READONLY(()->Validate.validState(false));
private Runnable action;
private UpdateState(Runnable action) {
this.action=action;
}
public T set(T value) {
action.run();
return value;
}
}
UpdateState 列舉用 Runnable 物件作為建構式引數。你可以實現更複雜的函式式介面滿足特定需求,複雜程度沒有上限。這裡只用到了普通的 Runnable 介面。UpdateState 列舉有兩種狀態:UPDATEABLE 和 READONLY。UPDATEABLE 驗證 true 的情況,而 READONLY 驗證結果為 false 的情況,這時會使用 Apache Commons Lang Validate 類丟擲 InvalidStateException 異常。
UpdateState 列舉有一個 set() 的方法,接受 value 引數後作為傳回值傳回。但是函式傳回 value 前,set() 會執行與 state 相關的 Runnable 操作。為什麼現在要變得這麼麻煩?
// 使用 state 樣式的購物車
public class ShoppingBasket {
private String orderNo;
private List articleNumbers = new ArrayList<>();
private UpdateState state = UpdateState.UPDATEABLE;
public void add(String articleNumber) {
articleNumbers.add(state.set(articleNumber));
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = state.set(orderNo);
}
public void order() {
// 實現訂單邏輯,如果下單成功,應改變訂單狀態
state = UpdateState.READONLY;
}
}
上面的程式碼中,ShoppingBasket 加入一個型別為 UpdateState 的 state 欄位,預設值為 UPDATEABLE。新建 ShoppingBasket 時,可以始終更新購物籃物件,表明訂單尚未提交。呼叫 order() 方法下單後,state 變為 READONLY。由於 state 變為只讀,所以 ShoppingBasket 行為將發生改變,特別是當客戶端嘗試訪問購物籃物件中的欄位時結果會發生改變。例如,讓我們看看 setOrderNo() 方法。setOrderNo() 不再直接將訂單號賦值給 orderNo,而是呼叫 UpdateState 列舉中的 set() 方法,透過該方法傳回設定的值,把傳回值賦值給 orderNo。 UpdateState 列舉中的 set() 方法會一直檢查是否允許更新。因此,當 ShoppingBasket 中 state 為 UPDATEABLE 時,set() 方法執行成功,但當 state 等於 READONLY 時,set() 方法將丟擲 IllegalStateException。 這正是我們一開始想要實現的,如果訂單提交成功,那麼物件變為只讀。
註意,可以根據需要實現複雜的 state 樣式。透過物件的狀態驅動物件的行為,這是一種非常優雅簡潔的方法,並且能夠在所有存取方法(accessor methor)中移除大量類似 if-else 這樣的非面向物件程式碼。假設有一個包含20個欄位的類,你肯定不希望在所有方法中每次都檢查狀態,這麼做會產生許多凌亂的程式碼。使用上面展示的 state 樣式,不但可以少寫很多程式碼而且會讓程式碼變得整潔起來。在上面例子的基礎上修改 UpdateState 列舉中的函式介面,你會發現可以用很少的程式碼來實現與狀態相關的行為。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能
喜歡就點「好看」唄~