如何用 Go 寫出優美的代碼 - Go 的設計模式【單例模式,工廠方法模式】篇一

大家好,我是追麾 (hui)。

接下來的幾周時間給大家分享一系列 Go 設計模式文章,設計模式在我們的面試中也會被經常問到,像 Java 語言會用到設計模式,對於 Go 語言,設計模式使用會比較弱點,所以這裏給大家一起來學習分享 Go 的設計模式,讓我們在開發中也大量應用到設計模式,幫助我們的 Go 代碼更加優美,可讀性更好。

第一篇主要分享兩種模式,單例模式和工廠方法模式。

Go 的單例模式

單例模式定義:Ensure a class has only one instance, and provide a global point of access to it.(確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。)

單例模式優缺點

單例模式的應用場景

單例模式實現方式

懶漢式:使用的時候才進行初始化,即懶加載

package main

func main() {
   // 調用實例對象
   GetInstance()
}

type Instance struct {
}

// 實例全局對象
var lazyInstance *Instance

func GetInstance() *Instance {
   // 判斷第一次如果爲空,則給lazy對象重新賦值。
   if lazyInstance == nil {
      lazyInstance = &Instance{}
   }
   return lazyInstance
}

在 Go 語言中,通過懶漢式來實現單例,重要點在判斷第一次實例化的對象爲空,則給對象重新賦值返回,這裏如果每次調用 GetInstance,出現併發問題,則會造成不少開銷,像這種問題主要是使用雙重檢測來解決。

餓漢式:在程序初始化的時候或者類加載的時候就已經創建好對象,加載速度快。

我們通過 Go 示例來看下餓漢式實現:

package main

func main() {
 GetInstance()
}

//對象
type Instance struct {
}

var lazyInstance *Instance

// 餓漢式:在程序初始化的時候或者類加載的時候就已經創建好對象,加載速度快。
func init() {
 lazyInstance = &Instance{}
}

// 餓漢式調用
func GetInstance() *Instance {
 return lazyInstance
}

在 Go 語言中,通過餓漢式來實現單例主要依賴 init 函數初始化變量,或者自己定義函數(函數內部初始化變量)在初始化程序或者加載包之前初始化,然後定義一個通用的方法對外服務。

雙重校驗:在懶漢式的基礎上,加上類級別的鎖。在 Go 語言中主要是基於 package 做全局鎖。

我們通過 Go 示例來看下雙重校驗實現:

package main

import "sync"

func main() {
 // 調用實例對象
 GetInstance()
}

type Instance struct {
}

// 實例全局對象
var lazyInstance *Instance

// 包級別的鎖
var once = &sync.Once{}

func GetInstance() *Instance {
 // 判斷第一次如果爲空,則給lazy對象重新賦值。
 if lazyInstance == nil {
  // once.Do只執行一次
  once.Do(func() {
   lazyInstance = &Instance{}
  })
 }
 return lazyInstance
}

靜態變量(Go 裏面通過常量來實現):Java 中利用了靜態內部類延遲初始化的特性,來達到與雙重校驗鎖方式一樣的功能,像 Go 中只能通過常量來實現,但是 Go 的常量僅支持整型,浮點型,字符串,bool 值,所以在 Go 中實現常量實現單例模式沒什麼意義。

枚舉類:該方式利用了枚舉類的特性,不僅能避免線程同步問題,還防止反序列化重新創建新的對象。在 Java 中是能實現,但是 Go 中是沒有這種,無法實現單例。

Go 的工廠方法模式

工廠方法模式定義:工廠方法(Factory Method)類定義產品對象創建接口,但由子類實現具體產品對象的創建。

工廠方法模式優缺點

工廠方法模式應用場景

工廠方法模式實現方式

在 Go 語言中,需要實現工廠方法模式,則需要使用到接口,定義一些公共的方法,用不同的 struct 對象來實現接口裏面公共的方法,struct 本身的對象的具體類型是不知道的,只有對象本身才知道。下面我們具體來看一個示例。

在很多聚合廣告業務中,我們需要調用不同的廣告廠商的接口,然後通過不同的廣告廠商的數據存到自己的業務系統中,這個時候就可以使用工廠方法模式來實現,首先一些公共的方法 RequestThirdApi(),每個廠商都要實現這些方法,每個廠商本身需要先按照自身的廣告廠商接口組裝請求數據,然後去請求,請求完了之後不同廠商接口返回的結構是不一樣的,結果不一樣,則可以通過處理成同樣結構的數據到我們自己的系統。下面我們來看下具體實現代碼:

package main

import "fmt"

func main() {
 NewFactory("kuaishou").RequestThirdApi()
}

// 做廣告投放需要拉取不同的廣告廠商,不同的廣告廠商是不一樣,但是都會存在請求第三方的方法
type ThirdResponseData struct {
}

// 工廠方法接口
type HttpRequestFactory interface {
 RequestThirdApi() *ThirdResponseData
}

// 頭條廠商 工廠類
type TouTiaoFactory struct {
}

func (t *TouTiaoFactory) RequestThirdApi() *ThirdResponseData {
 // 組裝請求
 // 執行請求
 // 處理請求響應結果,處理成ThirdResponseData這個的返回結構
 fmt.Println("頭條")
 return &ThirdResponseData{}
}

// 快手廠商 工廠類
type KuaiShouFactory struct {
}

func (t *KuaiShouFactory) RequestThirdApi() *ThirdResponseData {
 // 組裝請求
 // 執行請求
 // 處理請求響應結果,處理成ThirdResponseData這個的返回結構
 fmt.Println("快手")
 return &ThirdResponseData{}
}

// 用一個簡單工廠封裝工廠方法,這個是入口函數
func NewFactory(f string) HttpRequestFactory {
 switch f {
 case "kuaishou":
  return &KuaiShouFactory{}
 case "toutiao":
  return &TouTiaoFactory{}
 }
 return nil
}

上面只是一個簡單的版本,實際比這個複雜,我們會處理組裝請求,執行請求,響應都會用不同的方法來實現,這樣會看起來比較清晰。

關於 Go 的設計模式第一篇,就先分享到這裏。

參考文獻

《軟件設計模式之禪》

最後

如果這篇文章對您有所幫助,或者有所啓發的話,求一鍵三連:點贊、轉發、在看,您的支持是我堅持寫作最大的動力。

利志分享 分享技術,職場,人生感悟等,專注架構,go,k8s,kafka,clickhouse,個人小站:bbs.zengzhihai.com

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