## 7.13. 類型分支
接口被以兩種不同的方式使用。在第一個方式中,以io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler和error為典型,一個接口的方法表達了實現這個接口的具體類型間的相似性,但是隱藏了代碼的細節和這些具體類型本身的操作。重點在于方法上,而不是具體的類型上。
第二個方式是利用一個接口值可以持有各種具體類型值的能力,將這個接口認為是這些類型的聯合。類型斷言用來動態地區別這些類型,使得對每一種情況都不一樣。在這個方式中,重點在于具體的類型滿足這個接口,而不在于接口的方法(如果它確實有一些的話),并且沒有任何的信息隱藏。我們將以這種方式使用的接口描述為discriminated unions(可辨識聯合)。
如果你熟悉面向對象編程,你可能會將這兩種方式當作是subtype polymorphism(子類型多態)和 ad hoc polymorphism(非參數多態),但是你不需要去記住這些術語。對于本章剩下的部分,我們將會呈現一些第二種方式的例子。
和其它那些語言一樣,Go語言查詢一個SQL數據庫的API會干凈地將查詢中固定的部分和變化的部分分開。一個調用的例子可能看起來像這樣:
```go
import "database/sql"
func listTracks(db sql.DB, artist string, minYear, maxYear int) {
result, err := db.Exec(
"SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
artist, minYear, maxYear)
// ...
}
```
Exec方法使用SQL字面量替換在查詢字符串中的每個'?';SQL字面量表示相應參數的值,它有可能是一個布爾值,一個數字,一個字符串,或者nil空值。用這種方式構造查詢可以幫助避免SQL注入攻擊;這種攻擊就是對手可以通過利用輸入內容中不正確的引號來控制查詢語句。在Exec函數內部,我們可能會找到像下面這樣的一個函數,它會將每一個參數值轉換成它的SQL字面量符號。
```go
func sqlQuote(x interface{}) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
```
switch語句可以簡化if-else鏈,如果這個if-else鏈對一連串值做相等測試。一個相似的type switch(類型分支)可以簡化類型斷言的if-else鏈。
在最簡單的形式中,一個類型分支像普通的switch語句一樣,它的運算對象是x.(type)——它使用了關鍵詞字面量type——并且每個case有一到多個類型。一個類型分支基于這個接口值的動態類型使一個多路分支有效。這個nil的case和if x == nil匹配,并且這個default的case和如果其它case都不匹配的情況匹配。一個對sqlQuote的類型分支可能會有這些case:
```go
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
```
和(§1.8)中的普通switch語句一樣,每一個case會被順序的進行考慮,并且當一個匹配找到時,這個case中的內容會被執行。當一個或多個case類型是接口時,case的順序就會變得很重要,因為可能會有兩個case同時匹配的情況。default case相對其它case的位置是無所謂的。它不會允許落空發生。
注意到在原來的函數中,對于bool和string情況的邏輯需要通過類型斷言訪問提取的值。因為這個做法很典型,類型分支語句有一個擴展的形式,它可以將提取的值綁定到一個在每個case范圍內都有效的新變量。
```go
switch x := x.(type) { /* ... */ }
```
這里我們已經將新的變量也命名為x;和類型斷言一樣,重用變量名是很常見的。和一個switch語句相似地,一個類型分支隱式的創建了一個詞法塊,因此新變量x的定義不會和外面塊中的x變量沖突。每一個case也會隱式的創建一個單獨的詞法塊。
使用類型分支的擴展形式來重寫sqlQuote函數會讓這個函數更加的清晰:
```go
func sqlQuote(x interface{}) string {
switch x := x.(type) {
case nil:
return "NULL"
case int, uint:
return fmt.Sprintf("%d", x) // x has type interface{} here.
case bool:
if x {
return "TRUE"
}
return "FALSE"
case string:
return sqlQuoteString(x) // (not shown)
default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
```
在這個版本的函數中,在每個單一類型的case內部,變量x和這個case的類型相同。例如,變量x在bool的case中是bool類型和string的case中是string類型。在所有其它的情況中,變量x是switch運算對象的類型(接口);在這個例子中運算對象是一個interface{}。當多個case需要相同的操作時,比如int和uint的情況,類型分支可以很容易的合并這些情況。
盡管sqlQuote接受一個任意類型的參數,但是這個函數只會在它的參數匹配類型分支中的一個case時運行到結束;其它情況的它會panic出“unexpected type”消息。雖然x的類型是interface{},但是我們把它認為是一個int,uint,bool,string,和nil值的discriminated union(可識別聯合)
- 前言
- Go語言起源
- Go語言項目
- 本書的組織
- 更多的信息
- 致謝
- 入門
- Hello, World
- 命令行參數
- 查找重復的行
- GIF動畫
- 獲取URL
- 并發獲取多個URL
- Web服務
- 本章要點
- 程序結構
- 命名
- 聲明
- 變量
- 賦值
- 類型
- 包和文件
- 作用域
- 基礎數據類型
- 整型
- 浮點數
- 復數
- 布爾型
- 字符串
- 常量
- 復合數據類型
- 數組
- Slice
- Map
- 結構體
- JSON
- 文本和HTML模板
- 函數
- 函數聲明
- 遞歸
- 多返回值
- 錯誤
- 函數值
- 匿名函數
- 可變參數
- Deferred函數
- Panic異常
- Recover捕獲異常
- 方法
- 方法聲明
- 基于指針對象的方法
- 通過嵌入結構體來擴展類型
- 方法值和方法表達式
- 示例: Bit數組
- 封裝
- 接口
- 接口是合約
- 接口類型
- 實現接口的條件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表達式求值
- 類型斷言
- 基于類型斷言識別錯誤類型
- 通過類型斷言查詢接口
- 類型分支
- 示例: 基于標記的XML解碼
- 補充幾點
- Goroutines和Channels
- Goroutines
- 示例: 并發的Clock服務
- 示例: 并發的Echo服務
- Channels
- 并發的循環
- 示例: 并發的Web爬蟲
- 基于select的多路復用
- 并發的退出
- 示例: 聊天服務
- 基于共享變量的并發
- 競爭條件
- sync.Mutex互斥鎖
- sync.RWMutex讀寫鎖
- 內存同步
- 競爭條件檢測
- 示例: 并發的非阻塞緩存
- Goroutines和線程
- 包和工具
- 包簡介
- 導入路徑
- 包聲明
- 導入聲明
- 包的匿名導入
- 包和命名
- 工具
- 測試
- go test
- 測試函數
- 測試覆蓋率
- 基準測試
- 剖析
- 示例函數
- 反射
- 為何需要反射?
- reflect.Type和reflect.Value
- Display遞歸打印
- 示例: 編碼S表達式
- 通過reflect.Value修改值
- 示例: 解碼S表達式
- 顯示一個類型的方法集
- 幾點忠告
- 底層編程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判斷
- 通過cgo調用C代碼
- 幾點忠告
- 附錄
- 附錄A:原文勘誤
- 附錄B:作者譯者
- 附錄C:譯文授權
- 附錄D:其它語言