Go 代碼應用設計模式: 工廠模式

設計模式本來是開發人員多年來總結的一套問題的解決方案。早期在從事 C/C++ 開發之初時,瞭解過各種設計模式的思想,以及其實現的代碼。接下來我將結合着設計模式的講解,並將其用 Go 代碼實現。總結一套 Go 語言設計模式代碼片段,讀者可理解後舉一反三的應用到自己的開發實踐中。

設計模式包括創建型模式、結構型模式、行爲型模式。本節我們將從創建型模式開始講解。

工廠模式

我們來看一個實際的應用場景,假設你剛開始運營一家快遞公司,使用貨運汽車進行快遞運輸。你的代碼或許如下:

type Car struct {
}
func NewCar() *Car {
  return &Car{}
}

此時,由於你的公司發展迅速賺了不老些錢,你打算開闢航運線路進行快遞運輸,提升你企業在快遞行業的競爭力。這個時候該怎麼做呢?

我先來說說反例,很多公司早期發展迅速,爲了儘快滿足業務需求,沒有充分去設計代碼的實現,或許在 Car 結構裏面增加一些航運的方法,在 Newcar 裏面增加一些邏輯判斷返回滿足航運的對象。後期公司又要發展海運等, 那這個代碼就越來越不好維護了。

這裏只是假設了一個簡單的場景,但是在我們實際生成中的業務會比這複雜得多,當這樣的代碼越來越多了,自然整個系統的維護成本越來大。互聯網行業本來就人員流動頻繁,當接手該項目的新人在對業務不足夠了解時,又需要快速滿足業務,往往這個時候就埋下了雷。當系統埋的雷越來越多,維護人員流動也就越頻繁,導致惡性循環。而大家都知道這個問題的存在,也不會投入多少資源讓你去對整體代碼甚至整個業務線進行梳理。因爲業務是可以賺錢的,這個我們作爲程序員也要理解。

以上這段本來與本節講解的內容關係不大,但是我還是盡力的控制自己的情緒吐槽下。目前我從事的崗位正是這樣,真的頭大。

回到正題,那我們應該怎樣去解決上面說到的增加航運途徑的代碼邏輯呢?這裏就用到了工廠模式,其代碼如下:

// 運輸方式
type Delivery interface {
  DeliveryPackage() error
}
// 汽車
type Car struct {
}
func (c Car) DeliveryPackage() error {
  return nil
}
// 飛機
type Plane struct {
}
func (p Plane) DeliveryPackage() error {
  return nil
}
func New() *Delivery {
  return ...
}

其他語言實現工廠模式都是通過類的繼承來實現的,但是 Go 語言由於沒有繼承。我們利用 interface 模擬 Car 、Plane 繼承至 Delivery 來實現。工廠模式中,業務方(也就是調用 New 方法的業務)無需關注是空運還是貨運。其只管調用 DeliverPackage() 將貨發送出去。而未來若增加了航運模式,類似的只用增加船運方式的類(Go 中的 struct),而對貨運航運的業務沒有任何干擾。

通過以上實現可以很顯然的看出工廠模式的解決方案比講述的反例代碼維護上好得多。

講到這裏我仍然還想對工作中遇到的實際案例說說自己的見解。我認爲在一個函數內需要謹慎多次、嵌套使用 if 判斷語句。不僅是開發者需要注意,進行 Code Review 的代碼審覈者也得嚴格把控。因爲代碼中的 if 嵌套太多,後續對代碼的理解、業務的理解真的太難繞出來了。

來講一個實際的案例,我目前負責的國際電商相關的業務,而不同的地區有些業務規則或者業務功能就不一樣,在一個接口裏的函數全都是各種 if 對地區的判斷,有些地區某些功能是一樣的就會出現 if 裏面或關係。

當層層嵌套後,對於理解某個地區的業務確實很難進行。在某個時候,可能有需求對印度做一個什麼新功能。這個時候就太難了,在原來的基礎上改,很可能會影響其他的地區。就算是不影響其他地區,測試也得全地區覆蓋驗證到,這樣反倒影響了業務開展時間。

我認爲每個地區就抽象出自己的一個類,每個地區類處理該地區的邏輯,雖然某些地區的邏輯可能一致,也可以使用一些抽象出公共方法的處理方式來解決,甚至就複製粘貼代碼,我認爲都比所有的地區柔和在一起更好維護。

再回到工廠模式,我們可以把每個地區認爲是工廠模式生成的對象,在 New 的時候傳入地區參數就行,後續的邏輯就在對應地區的成員方法裏了。例如工廠模式代碼。

我們來看下如果沒有使用工廠模式,就像我說的所有的地區柔和在一起代碼是怎樣的呢?

func DeliveryPackage(deliveryType string) error {
  if deliveryType == "car" {
    // ...
  }else if deliveryType == "plane" {
    // ...
  } else if deliveryType == "ship" {
    // ...
  }else {
    return errors.New("invalied delivery type")
  }
  // ...
  if deliveryType == "car" || deliveryType == "ship" {
    // ....
    if ...
  }
  // ....
  if deliveryType == "car" || deliveryType == "plane" {
    // ....
  } 
  // ...
}

這樣不僅每個方法需要處理對 deliveryType 的判斷,同時你認爲你方便讀出 car 類型的或許方式需要經過哪些處理嗎?又或者是新來一個業務需要對 plane 類型做修改,能夠保證不影響其他的業務邏輯嗎?

這一節講解的模式比較少,可以說是大篇幅都在講錯誤的代碼案例,這也是給讀者傳達使用設計模式的重要性,當我們的代碼每一行、每一個函數、每一個類都是經過揣摩出來的,而不是一味的任性趕快。這樣才能使我們進步,同時也使整個項目更好的維護。設計模式正是是給我們的揣摩帶來靈感。

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