[Golang] interfaces
此篇為各筆記之整理,非原創內容,資料來源可見文末參考資料:
- 👍 Interfaces in Go @ medium > rungo
TL;DR
- interface 的概念有點像是的藍圖,先定義某個方法的名稱(function name)、會接收的參數及型別(list of argument type)、會回傳的值與型別(list of return types)。定義好藍圖之後,並不去管實作的細節,實作的細節會由每個型別自行定義實 作(implement)。
// 任何型別,只要符合定義規則的話,就可以被納入 bot interface 中
type bot interface {
// getGreeting 這個函式需要接收兩個參數(string, int),並回傳 (string, error) 才符合入會資格
getGreeting(string, int) (string, error)
// getBotVersion 這個函式需要回傳 float 才符合入會資格
getBotVersion() float64
}
Interface 是什麼?
透過 interface 可以定義一系列的 method signatures 來讓 Type 透過 methods 加以實作,也就是說 interface 可以用來定義 type 有哪些行為(behavior)。
interface 就像藍圖一樣,在裡面會定義函式的名稱、接收的參數型別以及最終回傳的資料型別,而 Type 只需要根據這樣的藍圖加以實作(implement)出這些方法。在 Go 中,Type 不需要明確使用 implement
關鍵字來說明它實作了哪個 interface,只要它符合了該 interface 中所定義的 method signature,就等於自動實作了該 interface。
舉例來說,定義「狗」的 interface 包含方法「走路」、「吠」,只要有一個 Type 它能夠提供「走路」和「吠」的方法,那個這個 Type 就自動實作(implement)了「狗」這個 interface,不需要額外使用 implement
關鍵字。
另外,任何資料型別,只要實作了該 interface 之後,都可以被視為該 interface 的 type(polymorphism)。
interface 可以被賦值
- interface 沒有被賦值前,其 type 和 value 都會是
nil
- interface 被賦值後,它的型別值會變成實作它的 Type 的型別和值
interface 可以被想成是帶有 (value, type)
的元組(tuple),當我們呼叫某個 interface value 的方法時,實際上就是將該 value 去執行與該 type 相同名稱的方法(method):
- interface 的變數「動態值(dynamic value / concrete value)」會是實作此 interface 的 Type 的 value
- interface 的變數「動態型別(dynamic type / concrete type)」會是實作此 interface 的 Type 的型別
- interface 沒有「靜態值(static value)」
- interface 的「靜態型別(static type)」,則是該 interface 的本身,例如
type Shape interface{}
,這個 interface 所建立的變數,其靜態型別即是Shape
interface 的
dynamic type
又稱作concrete type
,因為當我們想要存取該 interface 的型別時,它回傳的會是 dynamic value,原本的static type
會被隱藏。
從下面的例子可以看到,目前的 interface 因為尚未被賦值,所以會回傳的是 zero value,從這裡可以看到,interface 的 Type 和 value 的 zero value 都是 nil
:
// interface 沒有被賦值前,其 type 和 value 都會是 `nil`
type Shape interface {
Area() float64
Perimeter() float64
}
func main() {
var s Shape
fmt.Println("value of s is", s) // value of s is <nil>
fmt.Printf("type of s is %T\n", s) // type of s is <nil>
}
如果 interface 有賦值的話,則可以看到顯示的 dynamic type 和 dynamic value 會是實作該 interface 的 Type 的 method 和 value:
Rect
type 實作了Shape
interface- 當一個 type 實作(implement)了某個 interface 後,該 Type 產生的變數除了會是原本的 Type 外,也同時屬於該 interface type,及 polymorphism
- 當把 Rect 作為 Shape interface 的值後,Shape 的 Type(dynamic type)會變成
Rect
、Value(dynamic value)會變成 Rect 的值({3, 5}
)
// interface 被賦值後,它的型別值會變成實作它的 Type 的型別和值
type Shape interface {
Area() float64
}
// Rect 實作了 Shape interface
// Rect 所建立的變數同時會符合 Rect(Struct Type)和 Shape(Interface Type)
type Rect struct {
width float64
height float64
}
func (r Rect) Area() float64 {
return r.height * r.width
}
func main() {
var s Shape = Rect{3, 5}
fmt.Printf("(%T, %v) \n", s, s) // (main.Rect, {3 5})
fmt.Println(s.Area()) // 可以直接用 Shape interface 來呼叫方法
}
範例二:
// https://tour.golang.org/methods/11
type I interface {
M()
}
// Type T 實作了 I interface
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
// Type F 實作了 I interface
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"} // 把 type T 的值賦予給變數 i
fmt.Printf("(%v, %T)\n", i, i) // i 的 dynamic value 是 &{Hello}、 dynamic type 是 *main.T
i.M() // 意思是將 type T 對應的 value (&{Hello}) 來執行 type T 對應的 M 方法
i = F(math.Pi) // 把 type F 的值賦予給變數 i
fmt.Printf("(%v, %T)\n", i, i) // i 的 dynamic value 是 3.141、dynamic type 是 main.F
i.M() // 意思是將 type F 對應的 value (3.1415) 去執行 type F 對應的 M 方法
}
Interface 的 polymorphism
當一個 type 實作了 interface 後,這個 type 所建立的變數除了屬於原本的 type 之外,也屬於這個 interface 的 type,一個變數同時符合多個型別就稱作 polymorphism(多型)。
舉例來說,這裡先定義了 Salaried
這個 interface type,接著 Salary
這個 struct type 實作了 Salaried
中定義的 method signatures,因此 Salary
這個 struct type 也同時符合了 Salaried
interface type,這樣的行為稱作 polymorphism。
// https://medium.com/rungo/structures-in-go-76377cc106a2
type Salaried interface {
getSalary() int
}
// Salary 實作了 getSalary() 的方法,因此可以算是 Salaried type(polymorphism)
type Salary struct {
basic, insurance, allowance int
}
func (s Salary) getSalary() int {
return s.basic + s.insurance + s.allowance
}
type Employee struct {
firstName, lastName string
salary Salaried
}
func main() {
ross := Employee{
firstName: "Ross",
lastName: "Geller",
salary: Salary{
1100, 50, 50,
},
}
fmt.Println("Ross's salary is", ross.salary.getSalary())
}
Interface 會隱性的被 implement
一個 Type 可以透過實作(implement)某一 interface 中的方法來實踐該 interface。舉例來說:
type I interface {
M()
}
type T struct {
S string
}
// 這個方法指的是 type T 會實作 interface I
// 但並不需要開發者主動去宣告它
func (t T) M() {
fmt.Println(t.S)
}
func main() {
// i 的 dynamic type 是 T、dynamic value 是 "hello"
var i I = T{"hello"}
i.M()
}
同時符合多個 interfaces 的 Type
一個 type 可以也可能同時符合多個 interface,以下面的程式碼為例:
- Cube type 同時實作了 Shape 和 Object interface
- 可以把 Cube 指派給 Shape 或 Object interface 所建立的變數
- Shape interface 所建立的變數
s
可以使用s.Area()
的方法;Object interface 所建立的變數o
可以使用o.Volume()
的方法 - 雖然變數
s
和o
的 dynamic type 都是 Cube,但其底層的 Static Type 不同,前者是Shape interface
、後者是Object interface
,因此雖然它們的 dynamic type 都是 Cube type,但是並不能使用s.Volume()
和o.Area()
。
// 程式來源:https://medium.com/rungo/interfaces-in-go-ab1601159b3a
type Shape interface {
Area() float64
}
type Object interface {
Volume() float64
}
type Cube struct {
side float64
}
func (c Cube) Area() float64 {
return 6 * (c.side * c.side)
}
func (c Cube) Volume() float64 {
return c.side * c.side * c.side
}
func main() {
c := Cube{3}
var s Shape = c
var o Object = c
fmt.Println("volume of s of interface type Shape is", s.Area()) // 54
fmt.Printf("Shape (%T, %v) \n", s, s) //Shape (main.Cube, {3})
fmt.Println("area of o of interface type Object is", o.Volume()) // 27
fmt.Printf("Object (%T, %v) \n", o, o) // Object (main.Cube, {3})
}