第7章 接口
接口类型是对其它类型行为的抽象和概括.接口类型不会和特定的实现细节绑定在一起,这种抽象的方式能让我们的函数更加的灵活和更具有适应能力
Go语言的接口比较特殊,因为它是满足隐式实现的。也就是说,我们无需给具体类型定义所有满足足的接口类型,只需要让类型拥有一些简单必要的方法。这样我们新建一个接口类型,满足具体类型,并且我们不需要更改这些类型的定义。当我们使用的类型来自于不受我们控制的包时,这种机制比较有用
7.12 通过类型断言查询访问行为
下面的代码逻辑和net/http包中web服务器负责写入HTTP 头字段的部分相似。io.Writer 接口类型的变量w代表HTTP响应;写入它的字节最终会被发送到某个人的浏览器上
func writeHeader(w io.Writer,contentType string) error {
if _, err := w.Write([]byte("Content-Type: ")); err != nil {
return err
}
if _,err := w.Write([]byte(contentType));err != nil {
return err
}
//...
}
我们希望写入的值是字符串,但是Write方法需要传入的是一个byte切片,所以我们使用了[]type做转换,这个操作会分配内存做一个拷贝,这会影响程序的性能。回顾net/http,w变量持有的动态类型有一个允许字符串高效写入的WriteString方法,它会避免分配临时拷贝,许多满足io.Writer 接口的重要类型同时也有writeString方法,包括:*bytes.Buffer, *os.File 和 *bufio.Writer
尽管这样,我们不能对任意io.Writer类型的变量w,假设它也拥有WriteString方法。但是我们可以通过使用接口类型断言这种方法来检测他是否拥有,具体操作是定义一个只有WriteString方法的接口,然后断言
func writeString(w io.Writer, s string) (n int, err error) {
type stringWriter interface {
WriteString(string) (n int, err error)
}
if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s) //avoid copy
}
return w.Write([]byte(s))
}
func writeHeader(w io.Writer,contentType string) error {
if _,err := writeString(w,"Content-Type: "); err != nil {
return err
}
if _,err := writeString(w, "contentType"); err != nil {
return err
}
// ...
}
为了避免重复定义,我们将这个检查移入到一个实用工具函数writeString 中但是它太有用了以至于标准库将它作为io.WriteString函数提供这是向一个io.WriteString接口写入字符串的推荐方法
这个例子假设就是:一个类型满足下面这个接口,然后WriteString(s)方法就必须和Write([]byte(s))有相同的效果
interface {
io.Writer
WriteString(s string) (n int, err error)
}
这种类型断言机制用来检测某个类型是否实现了某个接口是非常有用的
这也是fmt.Fprintgf函数怎么从其它所有值中区分满足error或者fmt.Stringer接口的值,在fmt.Fprintf内部,有一个将单个操作对象转换成一个字符串的步骤,像下面这样:
package fmt
func formatOneValue(x interface{})string {
if err,ok := x.(error);ok {
return err.Error()
}
if str, ok := x.(Stringer);ok {
return str.String()
}
}
如果x满足这两个接口中的一个,具体满足的接口决定对值的格式化方式,如果都不满足,默认的case或多或少会统一地
使用反射来处理所有其它类型;我们会在后面12章看到具体是怎么实现的
再一次的,它假设
任何有String方法的类型都满足fmt.Stringer中约定的行为,这个行为会返回一个适合打印的字符串