## Go: Interface 在 Go 語言中,雖然不存在傳統物件導向程式設計(OOP)中的 class 或 inheritance 概念,但它提供了極其靈活的 interface 機制。 interface 提供了一種描述物件行為的抽象方式:它定義了一組物件應具備的方法,任何型別只要實現了這組方法,就能被視為該 interface 型別。 ### Basic concept interface 定義了一組方法的集合(method set),但這些方法本身不包含任何實作程式碼,它們是抽象的。此外 interface 內不能包含變數。 **syntax definition** ```go type Namer interface { Method1(param_list) return_type Method2(param_list) return_type ... } ``` - 在此範例中,Namer 是一個 **interface 型別**。 - **命名慣例**: 依照 Go 的慣例,只包含單一方法的 interface 名稱通常由方法名加上 er 後綴組成,例如 Printer、Reader、Writer。當 er 後綴不適用時,也可能以 able 結尾(如 Recoverable)或以 I 開頭(類似 .NET 或 Java 的風格)。 - **簡潔性**: Go 語言的 interface 設計崇尚簡潔,通常只包含 0 到 3 個方法。 ### Interface Values and Implicit Implementation interface 型別的變數被稱為 **interface value**。一個未初始化的 interface 變數,其值為 nil。 #### 介面值的內部結構 一個 interface value 在記憶體中是一個「多字」(multiword)的資料結構,它本質上是一個指標,但更為複雜。它主要包含兩個部分: - **值 (Value / Receiver)**: 一個指向該 interface 變數所儲存的實際值的指標。 - **方法表指標 (Method Table Ptr)**: 一個指向該值對應型別的方法表的指標。這個方法表是在執行期透過反射(runtime reflection)能力建構的。 #### 隱性實作 (Implicit Implementation) - **無需顯式聲明**: 一個型別不需要像 Java 或 C# 那樣使用 implements 關鍵字來聲明它實作了某個 interface。只要該型別的方法集包含了 interface 中定義的所有方法,Go 編譯器就認定它實作了該 interface。 - **其他主要特性**: 1. **多個型別可實作同一個 interface**。 2. **一個型別可實作多個 interface**。 3. 實作 interface 的型別可以擁有 interface 未定義的其他方法。 4. interface 是**動態型別**,它可以包含任何實作了此 interface 的實例引用。 5. 即使 interface 在型別之後才定義,或兩者分屬不同套件,只要方法集匹配,實作關係依然成立。 ### Basic Usage and Polymorphism interface 的核心應用之一是實現多型(polymorphism),即讓不同型別的物件對相同的訊息(方法呼叫)作出不同的反應。 #### 範例 1: 單一型別實作介面 在 interfaces.go 中,我們定義一個 Shaper 介面和一個 Square 結構體。 ```go package main import "fmt" type Shaper interface { Area() float32 } type Square struct { side float32 } // Square 實作了 Shaper,因為它有 Area() 方法 func (sq *Square) Area() float32 { return sq.side * sq.side } func main() { sq1 := new(Square) sq1.side = 5 // 將 *Square 型別的變數賦值給 Shaper 介面變數 var areaIntf Shaper areaIntf = sq1 // 也可以用更簡潔的寫法: // areaIntf := Shaper(sq1) // 或甚至 areaIntf := sq1 fmt.Printf("The square has area: %f\n", areaIntf.Area()) } ``` **輸出結果**: The square has area: 25.000000 - **說明**: *Square 型別定義了一個 Area() 方法,其簽名與 Shaper 介面完全一致,因此 *Square 隱性地實作了 Shaper。這使得我們可以將 sq1(一個 *Square 指標)賦值給 areaIntf(一個 Shaper 介面)。當呼叫 areaIntf.Area() 時,Go 執行期會根據 areaIntf 內部儲存的型別(*Square)去呼叫對應的 Area() 方法。 - **編譯期檢查**: 如果 Square 沒有實作 Area() 方法,編譯器會報錯。 #### 範例 2: 多型應用 在 interfaces_poly.go 中,我們新增 Rectangle 型別也實作 Shaper 介面,並將不同型別的物件放入一個 Shaper 切片中,展示多型行為。 ```go package main import "fmt" type Shaper interface { Area() float32 } type Square struct { side float32 } func (sq *Square) Area() float32 { return sq.side * sq.side } type Rectangle struct { length, width float32 } func (r Rectangle) Area() float32 { return r.length * r.width } func main() { r := Rectangle{5, 3} q := &Square{5} // 將不同型別但都實作 Shaper 的物件放入同一個 slice shapes := []Shaper{r, q} fmt.Println("Looping through shapes for area ...") for n, _ := range shapes { fmt.Println("Shape details: ", shapes[n]) fmt.Println("Area of this shape is: ", shapes[n].Area()) } } ``` **輸出結果**: Looping through shapes for area ... Shape details: {5 3} Area of this shape is: 15 Shape details: &{5} Area of this shape is: 25 **說明**: 儘管 shapes 陣列的元素型別都是 Shaper,但在迴圈中呼叫 shapes[n].Area() 時,Go 會動態地判斷 shapes[n] 當下儲存的具體型別(Rectangle 或 *Square),並執行該型別的 Area() 方法。這使得程式碼更具一般性與擴展性。 #### 範例 3: 更具體的應用 valuable.go 展示了如何定義一個通用函數,該函數可以處理任何實作了 valuable 介面的型別。 ```go package main import "fmt" type stockPosition struct { ticker string sharePrice float32 count float32 } func (s stockPosition) getValue() float32 { return s.sharePrice * s.count } type car struct { make string model string price float32 } func (c car) getValue() float32 { return c.price } // 定義一個契約,描述有「價值」的物件 type valuable interface { getValue() float32 } // 此函數接受任何實作 valuable 介面的物件 func showValue(asset valuable) { fmt.Printf("Value of the asset is %f\n", asset.getValue()) } func main() { var o valuable = stockPosition{"GOOG", 577.20, 4} showValue(o) o = car{"BMW", "M3", 66500} showValue(o) } ``` **輸出結果**: Value of the asset is 2308.800049 Value of the asset is 66500.000000 **說明**: stockPosition 和 car 都實作了 valuable 介面。showValue 函數的參數型別是 valuable,因此它可以接收這兩種不同型別的物件,並呼叫它們各自的 getValue() 方法。 ### Composing and Embedding Interfaces 一個介面可以嵌入(embed)一個或多個其他介面,這等同於將被嵌入介面的所有方法都包含在外層介面中。 ```go type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool } type Lock interface { Lock() Unlock() } // File 介面包含了 ReadWrite 和 Lock 的所有方法,以及 Close 方法 type File interface { ReadWrite Lock Close() } ``` ### Type Assertion and Type-Switch 由於 interface 變數可以儲存任何實作該 interface 的型別值,我們需要一種方式來檢測其在執行期的動態型別(dynamic type)。 #### Type Assertion 型別斷言(type assertion)用於檢查一個 interface 變數內部是否儲存了某個特定型別的值,並可以取得該值。 1. **不安全的型別斷言**: ```go v := varI.(T) // varI 必須是 interface 型別 // 如果 varI 內部儲存的不是 T 型別,此操作會引發一個 panic,導致程式崩潰。 ``` 2. **安全的型別斷言 (建議使用)**: ```go if v, ok := varI.(T); ok { // 轉換成功,v 是 T 型別的值,ok 為 true Process(v) // 若只關心型別是否匹配而不關心值,可使用:if _, ok := varI.(T); ok { ... } } else { // 轉換失敗,v 是 T 型別的零值,ok 為 false,不會 panic } ``` **範例: type_interfaces.go** ```go package main import ( "fmt" "math" ) type Shaper interface { Area() float32 } type Square struct { side float32 } func (sq *Square) Area() float32 { /* ... */ } type Circle struct { radius float32 } func (ci *Circle) Area() float32 { /* ... */ } func main() { var areaIntf Shaper sq1 := new(Square) sq1.side = 5 areaIntf = sq1 // 檢查 areaIntf 是否包含 *Square 型別 if t, ok := areaIntf.(*Square); ok { fmt.Printf("The type of areaIntf is: %T\n", t) } // 檢查 areaIntf 是否包含 *Circle 型別 if u, ok := areaIntf.(*Circle); ok { fmt.Printf("The type of areaIntf is: %T\n", u) } else { fmt.Println("areaIntf does not contain a variable of type Circle") } } ``` **輸出**: The type of areaIntf is: *main.Square areaIntf does not contain a variable of type Circle **注意**: 在範例中,斷言的目標是 *Square 而非 Square。因為 Area 方法的接收者是 *Square,所以只有 *Square 型別才實作了 Shaper 介面。若寫成 areaIntf.(Square),會導致編譯錯誤。 #### Type-Switch type-switch 是一個特殊形式的 switch 語句,專門用於判斷 interface 變數的動態型別。 ```go // 範例 (續上) switch t := areaIntf.(type) { case *Square: fmt.Printf("Type Square %T with value %v\n", t, t) case *Circle: fmt.Printf("Type Circle %T with value %v\n", t, t) case nil: fmt.Printf("nil value: nothing to check?\n") default: fmt.Printf("Unexpected type %T\n", t) } ``` **輸出**: Type Square *main.Square with value &{5} - **說明**: t 會被賦予 areaIntf 轉換後的值與型別。case 中列出的型別必須都實作 areaIntf 的介面。type-switch 不允許 fallthrough。 - **應用**: 在處理來自外部、型別未知的資料(如解析 JSON 或 XML)時非常有用。 #### 檢查值是否實作某介面 型別斷言的一個特例是檢查某個值 v 是否實作了某個 interface。 ```go type Stringer interface { String() string } if sv, ok := v.(Stringer); ok { fmt.Printf("v implements String(): %s\n", sv.String()) } ``` **說明**: v 本身不一定是 interface 型別。這個語法會檢查 v 的型別是否實作了 Stringer 介面。如果實作了,ok 為 true,且 sv 是一個 Stringer 介面變數,其值與 v 相同。 ### Method Sets and Interfaces 當一個值被賦予 interface 時,其方法集(method set)必須滿足介面要求。這與接收者(receiver)的型別(指標或值)有關。 **規則總結** - **指標接收者的方法**:可以透過指標呼叫。**不可以**透過值呼叫。因為 interface 內部儲存的值是不可尋址的(unaddressable),無法取得其位址來呼叫指標方法。 - **值接收者的方法**:可以透過值或指標呼叫(指標會被自動解引用)。 **Go 語言規範的定義**: - 型別 *T 的方法集包含接收者為 *T 或 T 的所有方法。 - 型別 T 的方法集僅包含接收者為 T 的方法,不包含接收者為 *T 的方法。 **範例: methodset.go** ```go package main import "fmt" type List []int func (l List) Len() int { return len(l) } // 值接收者 func (l *List) Append(val int) { *l = append(*l, val) } // 指標接收者 type Appender interface { Append(int) } func CountInto(a Appender, start, end int) { /* ... */ } type Lener interface { Len() int } func LongEnough(l Lener) bool { /* ... */ } func main() { // 值 var lst List // CountInto(lst, 1, 10) // 編譯錯誤! // List 型別沒有 Append 方法 (Append 的接收者是 *List) // 所以 List 沒有實作 Appender 介面 if LongEnough(lst) { // 合法: Len 的接收者是 List fmt.Printf("- lst is long enough\n") } // 指標 plst := new(List) CountInto(plst, 1, 10) // 合法: Append 的接收者是 *List if LongEnough(plst) { // 合法: *List 的方法集包含 Len (自動解引用) fmt.Printf("- plst is long enough\n") } } ``` ### Empty Interface 空介面不包含任何方法,因此任何型別都預設實作了空介面。 ```go type Any interface {} ``` - **用途**: 空介面 interface{} 常用於處理未知型別的資料,類似於 C# 或 Java 中的 Object。可以將任何型別的值賦予 interface{} 型別的變數。 - **記憶體**: 一個 interface{} 變數佔用兩個字長的記憶體:一個儲存值的型別,另一個儲存值本身或指向值的指標。 **範例: empty_interface.go** ```go package main import "fmt" type Person struct { name string age int } func main() { var val interface{} // Any 的別名 val = 5 fmt.Printf("val has the value: %v\n", val) val = "ABC" fmt.Printf("val has the value: %v\n", val) val = &Person{"Rob Pike", 55} fmt.Printf("val has the value: %v\n", val) // 使用 type-switch 檢查 val 的具體型別 switch t := val.(type) { case int: fmt.Printf("Type int %T\n", t) case *Person: fmt.Printf("Type pointer to Person %T\n", t) default: fmt.Printf("Unexpected type %T\n", t) } } ``` **輸出**: val has the value: 5 val has the value: ABC val has the value: &{Rob Pike 55} Type pointer to Person *main.Person #### 通用型別與異質容器 (Generic Types and Heterogeneous Containers) 空介面 interface{} 讓我們可以建立能儲存任何型別元素的容器,即「異質容器」。 - **通用 Vector 範例**: ```go type Element interface{} // 空介面的別名 type Vector struct { a []Element } func (p *Vector) Set(i int, e Element) { p.a[i] = e } ``` **注意**: 從這種容器中取出元素後,需要使用型別斷言將其還原為原始型別才能使用。 - **複製資料切片至空介面切片**: []myType 型別的切片不能直接賦值給 []interface{} 型別的變數,因為它們在記憶體中的佈局不同。必須透過 for-range 逐一複製。 ```go var dataSlice []myType = ... interfaceSlice := make([]interface{}, len(dataSlice)) for i, d := range dataSlice { interfaceSlice[i] = d } ``` --- Reference: https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.1.md