iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 19
1
自我挑戰組

Let's Eat GO ! 實務開發雜談by Golang系列 第 19

Day19 .[心得與討論篇] 走向interface去設計架構(6)- 進階變化

interface的介紹會在這篇先告個段落,因為一個精美的golang底層source code,主體和主體的介接和設計,不是光用interface就能一口氣解釋全部。

包括embedded、pointer、goruitne&channel等等,甚至資料結構教的link list概念也會穿插其中,當這些概念都會的話,基本上就大部分看懂golang的底層設計方式。

筆者認為,interface是一個很重要的部分,而且深入了解這塊,對於其他知識主題的理解也很有幫助,逐一破解,有天source code就不是你的障礙,而是優秀的導師。

今天來將前面幾篇的介紹做個總結,順便再說兩個筆者認為是進階的設計方式。

interface放進struct的用意

承Day17 最後的範例程式碼改寫,如下面再貼一遍

// Employee 員工
type Employee struct {
	id  int
	age int
}
// ITool 工具的method規格
type ITool interface {
	IsCanUse() bool
}

// UseTool 使用工具
func (e *Employee) UseTool(t ITool) error {
	isOk := t.IsCanUse()
	if !isOk {
		return errors.New("此工具使用壽命已盡,無法再使用")
	}
	return nil
}

其實外面的人來看你的code,若沒有用到Employee的UseTool(),其它他可能不會知道Employee(員工)與Tool(工具),兩種主體業務彼此的關係。

於是在他的腦袋中,要建立這兩者的理解,可能需要多費一點功夫。

若兩者主體是很緊密的關係,一開始就能用個方式宣告、暗示,無非在使用上會有莫大的幫助。

於是回到interface第一篇介紹的部分,DB struct裡面就放著connector的interface,現在就好理解。

DB一定要有個連線處理的業務主體,沒有這樣的連線主體,這個sql的package根本實際上沒路用,兩者本身在要做的事情就有很緊密的關係。

golang source code

type DB struct {
	
	waitDuration int64 

	connector driver.Connector
}

上面可以解讀成,DB擁有一個『屬於』它的connector主體業務,這主體業務是interface。

對於放在外面的interface只是規格,如我們的ITool。

// ITool 工具的method規格
type ITool interface {
	IsCanUse() bool
}

筆者認為有三點是我們設計上要關心的:

  1. 誰實現符合這規格的主體?

  2. 什麼時候產生這個實體?

  3. 這個實體給誰使用?

這三個問題可以來解答,為什麼把connector這個interface放在DB裡面的設計關係。

  1. 不論誰來實現,DB到時候會知道是誰,或者提供方法,讓實現的實體傳入DB做使用。

  2. 不用在產生DB這個實體的時候,實現connector的實體就要準備好,記得interface的zero value嗎?是nil,一開始是nil完全沒有關係,等到時機到了,產生實現connector的實體,再塞進來就好,就也是實現lazy initialization的好方法。

  3. 擺明的,DB這個主體業務會用,其他主體會不會用不知道,也沒關係,但你看到DB struct的時候,知道DB會使用它。

sql package 提供一個叫Register()的方法,讓connector有實體後丟到DB使用的做法,看你是mysql driver 還是oricle driver的connector,DB不關心細節,只關心connector裡面定義好的method。

// Register makes a database driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver driver.Driver) {
	driversMu.Lock()
	defer driversMu.Unlock()
	if driver == nil {
		panic("sql: Register driver is nil")
	}
	if _, dup := drivers[name]; dup {
		panic("sql: Register called twice for driver " + name)
	}
	drivers[name] = driver
}

type assertion 輔助

筆者忘記在哪個golang source code看到的使用方法,配合type assertion的使用,覺得也是可以參考一下。

我們知道interface,背後基本上還是會有個struct,這個struct有個符合interface的method,所以可以被視為某種interface類型。

如果我們的例子

// Tool 工具1
type Tool struct {
	id             string
	name           string
	power          float64
	weight         float64
	remainUsedTime int // 剩下可使用次數
}

// IsCanUse 可否使用
func (t *Tool) IsCanUse() bool {
	if t.remainUsedTime < 0 {
		return false
	}
	return true
}

// Tool2 工具2
type Tool2 struct {
	id       string
	name     string
	power    float64
	weight   float64
	UsedTime int // 使用過的次數
}

// IsCanUse 可否使用
func (t2 *Tool2) IsCanUse() bool {
	if t2.UsedTime >= 10000 {
		return false
	}
	return true
}

// ITool 工具的method規格
type ITool interface {
	IsCanUse() bool
}

如果今天改造employee的struct如下,在實現iTool的實體,你可能不知道實際上是Tool1或者Tool2。

type Employee struct {
	id  int
	iTool ITool
}

但是需要知道細部結構的時候,你可以撰寫某些判斷輔助,或設計上就某些根據你知道其實後面是哪種struct。

那麼就可以使用type assertion的方式,強制把interface轉成struct做使用。

if t, ok := iTool.(Tool2); !ok {
    fmt.Println("type assertion 失敗")    
 }

不過筆者還是提醒一下,該使用方式看各位的需求,但interface的設計理念和struct的理念不同,想好再這麼做。

另外type assertion有個預先判斷最好,如上面的寫法,否則失敗是會發生panic。


上一篇
Day18 .[心得與討論篇] 走向interface去設計架構(5)- 從思考資料的想法轉變成思考業務
下一篇
Day20 .[心得與討論篇] chain 鏈式寫法
系列文
Let's Eat GO ! 實務開發雜談by Golang30

尚未有邦友留言

立即登入留言