解釋 Golang 中的 Interface 到底是什麼

也許你是來自 PHP 或 Node.js 的程式工程師,然後用了 Class 好一段時間,雖然聽過 Interface 但總是沒用過,這很正常,因為我也是。

事實上,在弱型態語言其實是不需要使用到 Interface 的,但在強型態語言中,這變得十分普遍而且很有幫助。

Interface{} 是任意值

在 Golang 裏有兩種 Interface,一種是型態,另一種是定義,有些時候你可能會看見像下面這樣的 Interface。

func Hello(value interface{}) {  
}

在這種時候 interface{} 意味著任何型態的值,意思是你可以傳入 intstring 或者是建構體都行,這令你在設計程式時擁有足夠的彈性來接收任意型態的值。

型態斷言

別以為這樣就結束了,因為你傳入的東西現在成為了 interface{} 型態。因此你需要透過型態斷言(Type Assertion)來宣告這個 interface{} 的內容真正是什麼型態,如此一來才能夠在其他函式中用上。

func Hello(value interface{}) {  
    // 透過型態斷言揭露 interface{} 真正的型態。
    switch v := value.(type) {
        // 如果 value 是字串型態。
        case string:
            fmt.Println("value 是字串,內容是 " + v)
        // 如果 value 是 int 型態。
        case int:
            fmt.Printf("value 是數值,加上二就是 %d", v + 2)
    }
}

型態宣告

如果你很確越切地知道這個 interface{} 是什麼型態,你不需要大費周章地寫出一個型態斷言,你可以直接透過 value.(型態) 來進行型態宣告(Type Casting)並將 interface{} 變成指定型態。

倘若該型態不正確,則會出現 panic 警告。

func Hello(value interface{}) {  
    fmt.Println("value 是字串,內容是 " + value.(string))
}

Interface 是介面定義

這裡的介面指的不是 GUI 那種可見的畫面,而是指 Interface,中國稱其為「接口」,顧名思義,你會透過 Interface 定義一個接口並讓別人以其實作,聽起來霧矇矇嗎?這裡有個實際例子。

實際範例

假設你有個資料庫驅動函式,他支援寫入和讀取資料庫,但你不想把它寫死成僅支援 MySQL,所以你會透過 Interface 定義一個接口,像這樣。

// Database 是一個介面定義,用來讓第三方開發者定義自己的資料庫使用方式,就像一種規範。
type Database interface {  
    // Read 會從資料庫中讀取內容。
    Read()
    // Write 會將內容寫入至資料庫。
    Write()
}

這意味著我們現在可以遵循 Database 這個介面,開始支援更多的資料庫,像下面這樣。

// MySQL 會實作 Database 介面。
type MySQL struct {}  
func (m MySQL) Read() {}  
func (m MySQL) Write() {}

// MongoDB 會實作 Database 介面。
type MongoDB struct {}  
func (m MongoDB) Read() {}  
func (m MongoDB) Write() {}  

現在我們只要將 MySQLMongoDB 傳入 New() 函式,然後就會被實作成 Database 介面,接著就能夠使用 MySQL 或 MongoDB 資料庫進行讀取和寫入了!

// New 會將接收到的物件以 Database 實作,並且呼叫相關函式對資料庫進行操作。
func New(db Database) {  
    // 讀取資料庫。
    db.Read()
    // 寫入資料庫。
    db.Write()
}

// 將建構體傳入 New 就會被實作成 Database。
New(MySQL{})  
New(MongoDB{})  

實作條件

實作 Interface 的時候有件事情要注意,那就是欲實作的建構體必須要有 Interface 所定義的所有函式、接收參數、回傳值,否則 Golang 會表明無法實作該 Interface 因為缺少某某條件。

type Database interface {  
    Read(string) string
    Write(string)
}

這意味著 Read 一定要接收一個字串,然後回傳一個字串,而 Write 則必須接收一個字串。

有趣的是 Golang 還允許你在定義 Interface 的時候擺上參數名稱用以辨別,但實作的時候並不需要遵循這個參數名稱。

type Database interface {  
    Read(name string) string
    Write(data string)
}

後記

以前會以為 Interface 是多此一舉的型態,但後來改用 Golang 之後,就發現根本是開發套件的神器,因為你能透過 Interface 定義你的程式讓別人也可以接上自己的東西,又不會導致把自己的套件寫死只支援少許的功能。