Go 代碼應用設計模式: 狀態模式、策略模式

我們繼續學習行爲模式中的狀態模式

某個對象可能有很多的狀態,且伴隨着不同的狀態有着不同的行爲模式。而狀態模式正是用來處理多種狀態和行爲的關係的。

在電商系統的中,訂單有着非常多的狀態,而不同的狀態下訂單的行爲也是不同的。我們爲了簡化例子,假設訂單一共有以下四種狀態:

而圍繞着訂單的狀態有以下行爲:

我們還是從反例開始理解狀態模式。圍繞着這些裝的行爲,在我們的代碼中可能有着非常多的 if 或者 switch 語句來處理:

type Order struct {
  orderID string
  state   int
}
const (
  OrderAdded  = 1
  OrderPaied  = 2
  OrderSended = 3
  OrderRefund = 4
)
func addOrder() Order {
  return Order{
    orderID: "xxx",
    state:   OrderAdded,
  }
}
// 取消訂單
func (o *Order) refund() {
  switch o.state {
  case OrderAdded:
    // todo ...
  case OrderPaied:
    // todo ...
  case OrderSended:
    // todo ...
  case OrderRefund:
    // todo ...
  }
}

這裏只列舉出來了 refund 接口,需要根據訂單的不同狀態來做不同的處理。這裏我們的示例只是有 4 個狀態,而現實的電商系統狀態要多得多,這樣這個 switch 會特別的大(狀態多了,行爲也就更多,每個行爲中的 switch 都特別大),影響代碼閱讀。

我們來看看狀態模式如何處理以上邏輯呢?

type OrderAddedState struct {
  order Order
}
type OrderPaiedState struct {
  order Order
}
type OrderSendedState struct {
  order Order
}
type OrderRefundState struct {
  order Order
}
func (o *OrderAddedState) Refund() error {
  //更新定單狀態爲已關閉
  return nil
}
func (o *OrderPaiedState) Refund() error {
  // 發起退款
  //更新定單狀態爲已關閉
  return nil
}
func (o *OrderSendedState) Refund() error {
  // 取消發貨
  // 發起退款
  // 更新定單狀態爲已關閉
  return nil
}
func (o *OrderRefundState) Refund() error {
  return errors.New("order has been refunded")
}

我們可以看到在不同的狀態下的取消訂單,封裝到獨立的類中去了,同時不同狀態下的其他行爲也可保證一樣的處理,這樣在不同狀態下的行爲需要處理哪些邏輯將一目瞭然,而無序去讀很長且複雜的 siwtch 代碼。

接下來我們瞭解行爲模式中的策略模式

策略模式可以使對象的行爲可以靈活的在多個算法之間切換。我們還是用電商這個實際案例來了解策略模式。假設商品的活動有直接折扣、加價購、套裝等銷售策略,如何在下單時計算這個商品的價格呢?

我們還是從反例來說明以上邏輯,通常我們還是會用多個 if else 來根據不同的銷售模式來計算商品的價格。且活動的優先級就已經通過 if 的層級進行劃分,而如果不同的商品活動優先級不同,又需要額外的 if 來進行判斷。從而使整個計算邏輯在多個且多層 if else 中處理,後期的維護越來越難。

我們看下策略模式是如何實現這個電商的實際場景的,首先我們定義計算邏輯的接口:

type orderAlgo struct {
}
type evictionAlgo interface {
  evict(orderAlgo)
}

直接折扣、加價購、套裝等具體的算法類將實現這個接口:

type cutoff struct {
}
type addPrice struct {
}
type suit struct {
}
func (cutoff) calc(calcuInfo) {
  fmt.Println("普通折扣計算")
}
func (addPrice) calc(calcuInfo) {
  fmt.Println("加價購折扣計算")
}
func (suit) calc(calcuInfo) {
  fmt.Println("套裝摺扣計算")
}

這樣每一個算法的邏輯在自己的成員函數中處理,而無需關注其他的計算邏輯,且不會像反例中一樣所有的計算邏輯柔和在一起。同時多個活動的優先級可由計算算法的調用先後次序決定。

但是這裏無法處理經過某個優惠後無法進行下一個優惠的實際場景,例如普通折扣後的商品無法再參與到套裝活動中,那麼需要調用方進行一系列的邏輯處理,例如給每種活動加上檢查函數,先檢查再調用。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4ffqh5sBZgUpBii8mWUnxA