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 的类型, 这会导致对这些库的依赖.
没有一种方法来检查未知类型的表示方式, 我们被卡住了. 这就是我们为何需要反射的原因.