## 5.11\. 接口和其他类型
### 5.11.1\. 接口
Go中的接口提供了一类对象的抽象。我们在前面已经看到了关于接口的一些例子。 我们可以给新定义的对象实现一个String方法,这样就可以用 Fprintf输出该类型的值。同样,Fprintf可以将 结果输出到任意实现了Write方法的对象。接口一般只包含一类方法, 并且以ed后缀的方式命名,例如io.Writer接口对应Write 方法实现。
一种类型可以实现多个接口。例如,如果想支持sort包中的排序 方法,那么只需要实现sort.Interface接口的Len()、 Less()、Swap(i, j int)方法就可以了。下面的例子 实现了sort.Interface接口的同时,还定制了输出函数。
```
type Sequence []int
// Methods required by sort.Interface.
func (s Sequence) Len() int {
return len(s)
}
func (s Sequence) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
sort.Sort(s)
str := "["
for i, elem := range s {
if i > 0 {
str += " "
}
str += fmt.Sprint(elem)
}
return str + "]"
}
```
### 5.11.2\. 转换
Sequence 的 String 方法重做了 Sprint 在切片的工作。我们可以在调用 Sprint 前把 Sequence 转为普通的 []int。
```
func (s Sequence) String() string {
sort.Sort(s)
return fmt.Sprint([]int(s))
}
```
转换使得 s 被当作普通的切片,因此得到默认的排版。不加转换,Sprint 会发现 Sequence 的 String 方法,进而无穷递归。如果忽略类型名称,Sequence 和 []int 的类型相同, 它们之间的转换是合法的。转换不会得到新值,它只是暂时假装现有值是新类型。(其它合法的转换,如从整型到浮点型,会生成新值。)
地道的 Go 程序会转换表达式的类型来使用一组不同的方法。例如,我们可以用现有类型 sort.IntArray 把整个例子缩减为:
```
type Sequence []int
// Method for printing - sorts the elements before printing
func (s Sequence) String() string {
sort.IntArray(s).Sort()
return fmt.Sprint([]int(s))
}
```
现在,无需让 Sequence 实现多个界面(排序和打印),我们使用了把数据转换为多种类型(Sequence,sort.IntArray 和 []int)的能力,每个来完成一部分的工作。这实际上不常见但很有效。
### 5.11.3\. Generality(泛化)
如果某类型的存在只为了实现某界面,而除此之外没有其它导出的方法,则此类型也不需导出。只导出界面明确了只有行为有价值,而不是实现,其它的不同特性的实现可以镜像其原来的类型。这样也避免了为同一方法的不同实现做文档。
此时,架构函数应返回界面而不是实现类型。例如,哈希库的crc32.NewIEEE() 和 adler32.New() 都返回界面类型 hash.Hash32。 替换一个 Go 出现的 CRC-32 算法为 Adler-32 只需改变架构函数的调用;其余的代码不受算法改变的影响。
同样的方式使得 crypto/block 包的流密(streaming cipher)算法与链接在一起的块密(block cipher)相区隔。比对 bu?o 包,它们包装了界面,返回 hash.Hash,io.Reader 和 io.Writer 界面值,而不是特定的实现。
crypto/block 界面包括:
```
type Cipher interface {
BlockSize() int
Encrypt(src, dst []byte)
Decrypt(src, dst []byte)
}
// NewECBDecrypter returns a reader that reads data
// from r and decrypts it using c in electronic codebook (ECB) mode.
func NewECBDecrypter(c Cipher, r io.Reader) io.Reader
// NewCBCDecrypter returns a reader that reads data
// from r and decrypts it using c in cipher block chaining (CBC) mode
// with the initialization vector iv.
func NewCBCDecrypter(c Cipher, iv []byte, r io.Reader) io.Reader
```
NewECBDecrypter 和 NewCBCReader 不只用于某特定的加密算法和数据源,而是任意的 Cipher 界面的实现和任意的 io.Reader。因为它们返回 io.Reader 界面值,替换 ECB 加密为 CBC 加密只是局部修改。 架构函数必须编辑,但因为周围代码必须只把结果作为io.Reader,它不会注意到有什么不同。
### 5.11.4\. 接口和方法
因为几乎任何东西都可加以方法,几乎任何东西都可满足某界面。一个展示的例子是 http 包定义的 Handler 界面。任何物件实现了Handler 都可服务 HTTP 请求。
```
type Handler interface {
ServeHTTP(*Conn, *Request)
}
```
ResponseWriter 本身是个界面,它提供一些可访问的方法来返回客户的请求。这些方法包括标准的 Write 方法。因此 http.ResponseWriter 可用在 io.Writer 可以使用的地方。Request 是个结构,包含客户请求的一个解析过的表示。
为求简短,我们忽略 POST 并假定所有 HTTP 请求都是 GET;此简化不会影响经手者的设置。下面一个小而全的经手者实现了网页访问次数的计数。
```
// Simple counter server.
type Counter struct {
n int
}
func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
ctr.n++
fmt.Fprintf(c, "counter = %d\n", ctr.n)
}
```
(注意 Fprintf 怎样打印到 http.ResponseWriter)。作为参考,这里是怎样把服务者加在一个 URL 树的节点上。
```
import "http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)
```
可是为何把 Counter 作为结构呢?一个整数能够了。(接受者需是指针,使增量带回调用者)。
```
// Simpler counter server.
type Counter int
func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
*ctr++
fmt.Fprintf(c, "counter = %d\n", *ctr)
}
```
当某页被访问时怎样通知你的程序更新某些内部状态呢?给网页贴个信道。
```
// A channel that sends a notification on each visit.
// (Probably want the channel to be buffered.)
type Chan chan *http.Request
func (ch Chan) ServeHTTP(c *http.Conn, req *http.Request) {
ch <- req
fmt.Fprint(c, "notification sent")
}
```
最后,让我们在 /args 显示启动服务器时的参量。写个打印参量的函数很容易:
```
func ArgServer() {
for i, s := range os.Args {
fmt.Println(s)
}
}
```
怎样把它变成 HTTP 服务器呢?我们可以把 ArgServer 作为某个类型的方法再忽略其值,也有更干净的做法。既然我们可以给任意非指针和界面的类型定义方法,我们可以给函数写个方法。http 包里有如下代码:
```
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(*Conn, *Request)
// ServeHTTP calls f(c, req).
func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
f(c, req)
}
```
HandlerFunc 是个带 ServeHTTP 方法的类型, 所以此类的值都可以服务 HTTP 请求。我们来看看此方法的实现:接受者是个函数,f,方法调用 f 。看起来很怪,但和,比如,接受者是信道,而方法发送到 此信道,没什么不同。
要把 ArgServer 变为 HTTP 服务器, 我们首先改成正确的签名:
```
// Argument server.
func ArgServer(c *http.Conn, req *http.Request) {
for i, s := range os.Args {
fmt.Fprintln(c, s)
}
}
```
ArgServer 现在和 HandlerFunc 有同样的签名,就可以转成此类使用其方法,就像我们把 Sequence 转为 IntArray 来使用 IntArray.Sort 一样。设置代码很简短:
```
http.Handle("/args", http.HandlerFunc(ArgServer))
```
当有人访问 /args 页时,此页的经手者有值 ArgServer 和类型HandlerFunc。HTTP 服务器启动此类型的 ServeHTTP 方法,用ArgServer 作为接受者,反过来调用 ArgServer (通过启动handlerFunc.ServeHTTP 的 f(w, req) 。)参量被显示出来。
此节中我们从一个结构,整数,信道和一个函数制造出一个 HTTP 服务器,全赖于界面就是一套方法,可定义在(几乎)任何类型上。
- 1. 关于本文
- 2. Go语言简介
- 3. 安装go环境
- 3.1. 简介
- 3.2. 安装C语言工具
- 3.3. 安装Mercurial
- 3.4. 获取代码
- 3.5. 安装Go
- 3.6. 编写程序
- 3.7. 进一步学习
- 3.8. 更新go到新版本
- 3.9. 社区资源
- 3.10. 环境变量
- 4. Go语言入门
- 4.1. 简介
- 4.2. Hello,世界
- 4.3. 分号(Semicolons)
- 4.4. 编译
- 4.5. Echo
- 4.6. 类型简介
- 4.7. 申请内存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印输出
- 4.13. 生成素数
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 简介
- 5.2. 格式化
- 5.3. 注释
- 5.4. 命名
- 5.5. 分号
- 5.6. 控制流
- 5.7. 函数
- 5.8. 数据
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他类型
- 5.12. 内置
- 5.13. 并发
- 5.14. 错误处理
- 5.15. Web服务器
- 6. 如何编写Go程序
- 6.1. 简介
- 6.2. 社区资源
- 6.3. 新建一个包
- 6.4. 测试
- 6.5. 一个带测试的演示包
- 7. Codelab: 编写Web程序
- 7.1. 简介
- 7.2. 开始
- 7.3. 数据结构
- 7.4. 使用http包
- 7.5. 基于http提供wiki页面
- 7.6. 编辑页面
- 7.7. template包
- 7.8. 处理不存在的页面
- 7.9. 储存页面
- 7.10. 错误处理
- 7.11. 模板缓存
- 7.12. 验证
- 7.13. 函数文本和闭包
- 7.14. 试试!
- 7.15. 其他任务
- 8. 针对C++程序员指南
- 8.1. 概念差异
- 8.2. 语法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 构造值对象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 内存模型
- 9.1. 简介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 错误的同步方式
- 10. 附录
- 10.1. 命令行工具
- 10.2. 视频和讲座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相关资源