## 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