Go 代碼應用設計模式: 抽象工廠模式、創建者模式

上一節我們只講了創建型設計模式中的工廠模式,更多的是在吐槽沒有經過設計的代碼實現會給未來造成怎樣的麻煩。由於我們當前行業的人員流動性確實比較大,能夠始終如一的對代碼高要求、完善管理的公司確實不多了。只希望作爲開發者的我們,始終對自己高要求,有着一顆追求技術的心。

把寫代碼當作在塑造藝術品的過程,寫出來的代碼自然也是一件藝術品。當他人再次閱讀你的代碼時,會驚歎多麼美好,而不是一坨💩。

本節我們將繼續講解創建型設計模式中的抽象工廠模式

由於 Go 沒有繼承的概念,在上一節中使用 interface 來代替基類的作用。上一節的代碼示例已然有了抽象工廠的概念。

抽象工廠模式能夠創建基於基類繼承的或者實現某個接口的一系列對象,而無需知道具體實現的是哪個類。

回顧下上節的例子,使用 New 方法(工廠)生產出來的對象是 Delivery 接口,但是具體是 Plane 還是 Car 對於使用者是無感的。

// 運輸方式
type Delivery interface {
  DeliveryPackage() error
}
func New() *Delivery {
  return ...
}

我們對這個例子進一步擴充,某個運送鏈路不僅僅只有運送包裹這一個方法,可能還有審覈、獲取運輸時間等方法:

// 運輸方式
type Delivery interface {
  CheckPackage() error
  DeliveryPackage() error
  GetDeliveryTime() (int64, error)
}

而航空運輸同貨運肯定有着不同的實現方式,那麼各自類代碼如下:

// 汽車
type Car struct {
}
func (c Car) CheckPackage() error {
  fmt.Println("貨運貨件檢查")
  return nil
}
func (c Car) DeliveryPackage() error {
  fmt.Println("貨運貨件運輸")
  return nil
}
func (c Car) GetDeliveryTime() (int64, error) {
  return 86400*3, nil
}
// 飛機
type Plane struct {
}
func (p Plane) CheckPackage() error {
  fmt.Println("航空貨件檢查")
  return nil
}
func (p Plane) DeliveryPackage() error {
  fmt.Println("航空貨件運輸")
  return nil
}
func (p Plane) GetDeliveryTime() (int64, error) {
  return 86400, nil
}

工廠模式和抽象工廠模式的區別在於是繼承還是接口實現,其原理都是一樣的即用戶無需關注生成的對象的具體類型****,只需要使用該對象完成這些對象共有方法的調用

由於 Go 沒有繼承的概念,所以工廠模式和抽象工廠模式在 Go 代碼上的體現是一樣的,本節的代碼案例相較於上一節僅僅只是對具體的運輸方式增加了方法,使其更接近生產實際中而已。

接下來我們看下創建型設計模式的另一種創建者模式

我們還是從案例入手,假設需要代碼模擬一家汽車生產商生產多個車型,每輛車都有汽車必備的:四個輪胎、座椅、發動機等,只是他們的品牌不同而已。每種車型也都有自己的選配裝置如倒車影像、座椅加熱、天窗等你會如何構造這個對象呢?

我們還是先來看反例代碼,把這三個手機的所有屬性都堆放在構造方法中,無論是哪一部手機的特性都會在構造代函數里,例如如下代碼:

func NewPhone(tyre, seat, engine string, withBackupCamera, withSeatHeat, withTopWindow))

可能選配裝置的裝置不止這些,那構造方法的參數會比這個多得多,當參數越來越多了,再加上命名不規範又沒有註釋。無論開發者自己或者後續的維護人員根本就不太好理解每個參數了,當每次構造一個汽車對象都得研究很久構造方法的參數。

這個時候你就得使用創建者模式了,我們可以把共有的屬性放在構造函數中,把特有的屬性封裝到每種類型的方法中作爲構造步驟。

type Car interface {
  DecorateBackupCamera() error
  DecorateTopWindow() error
  DecorateSeatHeat() error
}
type Car1 struct {
}
type Car2 struct {
}
func NewCar(tyre, seat, engine string) *Car {
  // ...
}
func (c Car1) DecorateBackupCamera() error {
  fmt.Println("car with BackupCamera")
  return nil
}
func (c Car1) DecorateTopWindow() error {
  return nil
}
func (c Car1) DecorateSeatHeat() error {
  fmt.Println("car with DecorateSeatHeat")
}
func (c Car2) DecorateBackupCamera() error {
  return nil
}
func (c Car2) DecorateTopWindow() error {
  fmt.Println("car with DecorateTopWindow")
  return nil
}
func (c Car2) DecorateSeatHeat() error {
  return nil
}

上述案例中, Car1 車型有配置倒車影像和座椅加熱,Car2 有配置天窗。由於 Go 語言 interface 的定義是實現了接口所有定義的方法也就實現了接口。所以在語言層面上 Car1、Car2 雖然都有沒有配置的選裝,但是都得實現對應的方法,在這裏是直接返回 nil 了。

可能代碼比較繁瑣,如果選裝項特別的多那麼類的成員函數也就更多。讓代碼看上去比較多,但是很直觀的通過函數體是否爲空很容易查看某款車型是否具備某個選裝。

創建者模式傳達的思想是不要在構造函數中傳入過多的參數,可以把熟悉的封裝放入不同的成員函數中,使對象看起來像是一步步組裝起來的而不是一鍋粥的煮出來的。(就算是煮粥也有食材的先後順序)

即使是某個普通的函數,也需要注意不要傳入過多的參數。參數多,要麼是參數冗餘了,很多參數根本就沒有用到;要麼是這個函數中處理的邏輯太大了,也不方便代碼的維護,可以多思考下是否可以拆成多步(多個函數)來處理,那麼每個函數只需要處理自己邏輯的參數,自然參數也就減少了。

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