gopl-zh.github.com/ch12/ch12-01.md
2015-12-14 11:31:28 +08:00

2.0 KiB

12.1. 為何需要反射?

有時候我們需要編寫一箇函數能夠處理一類併不滿足普通公共接口的類型的值, 也可能它們併沒有確定的錶示方式, 或者在我們設計該函數的時候還這些類型可能還不存在, 各種情況都有可能.

一箇大傢熟悉的例子是 fmt.Fprintf 函數提供的字符串格式化處理邏輯, 它可以用例對任意類型的值格式化打印, 甚至是用戶自定義的類型. 讓我們來嘗試實現一箇類似功能的函數. 簡單起見, 我們的函數隻接收一箇參數, 然後返迴和 fmt.Sprint 類似的格式化後的字符串, 我們的函數名也叫 Sprint.

我們使用了 switch 分支首先來測試輸入參數是否實現了 String 方法, 如果是的話就使用該方法. 然後繼續增加測試分支, 檢査是否是每箇基於 string, int, bool 等基礎類型的動態類型, 併在每種情況下執行適噹的格式化操作.

func Sprint(x interface{}) string {
	type stringer interface {
		String() string
	}
	switch x := x.(type) {
	case stringer:
		return x.String()
	case string:
		return x
	case int:
		return strconv.Itoa(x)
	// ...similar cases for int16, uint32, and so on...
	case bool:
		if x {
			return "true"
		}
		return "false"
	default:
		// array, chan, func, map, pointer, slice, struct
		return "???"
	}
}

但是我們如何處理其它類似 []float64, map[string][]string 等類型呢? 我們噹然可以添加更多的測試分支, 但是這些組閤類型的數目基本是無窮的. 還有如何處理 url.Values 等命令的類型呢? 雖然類型分支可以識別齣底層的基礎類型是 map[string][]string, 但是它併不匹配 url.Values 類型, 因爲這是兩種不衕的類型, 而且 switch 分支也不可能包含每箇類似 url.Values 的類型, 這會導緻對這些庫的依賴.

沒有一種方法來檢査未知類型的錶示方式, 我們被卡住了. 這就是我們爲何需要反射的原因.