高效GO语言编程(9)——通用性,接口和方法

欢迎加入GolangRoadmap,一个年轻的GO开发者社区https://www.golangroadmap.com/,目前是邀请制注册,注册码:Gopher-1035-0722,已开放GO内推,GO面试,GO宝典,GO返利等栏目

通用性

若某种现有的类型仅实现了一个接口,且除此之外并无可导出的方法,则该类型本身就无需导出。 仅导出该接口能让我们更专注于其行为而非实现,其它属性不同的实现则能镜像该原始类型的行为。 这也能够避免为每个通用接口的实例重复编写文档。

在这种情况下,构造函数应当返回一个接口值而非实现的类型。例如在 hash 库中,crc32.NewIEEE 和 adler32.New 都返回接口类型 hash.Hash32。要在Go程序中用Adler-32算法替代CRC-32, 只需修改构造函数调用即可,其余代码则不受算法改变的影响。

同样的方式能将 crypto 包中多种联系在一起的流密码算法与块密码算法分开。 crypto/cipher 包中的 Block 接口指定了块密码算法的行为, 它为单独的数据块提供加密。接着,和 bufio 包类似,任何实现了该接口的密码包都能被用于构造以 Stream 为接口表示的流密码,而无需知道块加密的细节。

crypto/cipher 接口看其来就像这样:

type Block interface {
    BlockSize() int
    Encrypt(dst, src []byte)
    Decrypt(dst, src []byte)
}

type Stream interface {
    XORKeyStream(dst, src []byte)
}

这是计数器模式CTR流的定义,它将块加密改为流加密,注意块加密的细节已被抽象化了。

// NewCTR 返回使用中给定的块加密/解密的流
// 计数器模式。iv 的长度必须与块的块大小相同
func NewCTR(block Block, iv []byte) Stream

NewCTR 的应用并不仅限于特定的加密算法和数据源,它适用于任何对 Block 接口和 Stream 的实现。因为它们返回接口值, 所以用其它加密模式来代替CTR只需做局部的更改。构造函数的调用过程必须被修改, 但由于其周围的代码只能将它看做 Stream,因此它们不会注意到其中的区别。

接口和方法

由于几乎任何类型都能添加方法,因此几乎任何类型都能满足一个接口。一个很直观的例子就是 http 包中定义的 Handler 接口。任何实现了 Handler 的对象都能够处理HTTP请求。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ResponseWriter 接口提供了对方法的访问,这些方法需要响应客户端的请求。 由于这些方法包含了标准的 Write 方法,因此 http.ResponseWriter 可用于任何 io.Writer 适用的场景。Request 结构体包含已解析的客户端请求。

为简单起见,我们假设所有的 HTTP 请求都是GET方法,而忽略POST方法, 这种简化不会影响处理程序的建立方式。这里有个短小却完整的处理程序实现, 它用于记录某个页面被访问的次数。

// 简单的计数器服务器。
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ctr.n++
    fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

(紧跟我们的主题,注意 Fprintf 如何能输出到 http.ResponseWriter。) 作为参考,这里演示了如何将这样一个服务器添加到URL树的一个节点上。

import "net/http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

但为什么 Counter 要是结构体呢?一个整数就够了。(接收者必须为指针,增量操作对于调用者才可见。)

// 简单的计数器服务。
type Counter int

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    *ctr++
    fmt.Fprintf(w, "counter = %d\n", *ctr)
}

当页面被访问时,怎样通知你的程序去更新一些内部状态呢?为Web页面绑定个信道吧。

// 每次浏览该信道都会发送一个提醒。
// (可能需要带缓冲的信道。)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ch <- req
    fmt.Fprint(w, "notification sent")
}

最后,假设我们需要输出调用服务器二进制程序时使用的实参 /args。 很简单,写个打印实参的函数就行了。

func ArgServer() {
    fmt.Println(os.Args)
}

我们如何将它转换为HTTP服务器呢?我们可以将 ArgServer 实现为某种可忽略值的方法,不过还有种更简单的方法。 既然我们可以为除指针和接口以外的任何类型定义方法,同样也能为一个函数写一个方法。 http 包包含以下代码:

// HandlerFunc 类型是一个适配器,
// 它允许将普通函数用做HTTP处理程序。
// 若 f 是个具有适当签名的函数,
// HandlerFunc(f) 就是个调用 f 的处理程序对象。
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
    f(w, req)
}

HandlerFunc 是个具有 ServeHTTP 方法的类型, 因此该类型的值就能处理HTTP请求。我们来看看该方法的实现:接收者是一个函数 f,而该方法调用 f。这看起来很奇怪,但不必大惊小怪, 区别在于接收者变成了一个信道,而方法通过该信道发送消息。

为了将 ArgServer 实现成HTTP服务器,首先我们得让它拥有合适的签名。

// 实参服务器。
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}

ArgServer 和 HandlerFunc 现在拥有了相同的签名, 因此我们可将其转换为这种类型以访问它的方法,就像我们将 Sequence 转换为 IntSlice 以访问 IntSlice.Sort 那样。 建立代码非常简单:

http.Handle("/args", http.HandlerFunc(ArgServer))

当有人访问 /args 页面时,安装到该页面的处理程序就有了值 ArgServer 和类型 HandlerFunc。 HTTP服务器会以 ArgServer 为接收者,调用该类型的 ServeHTTP 方法,它会反过来调用 ArgServer(通过 f(c, req)),接着实参就会被显示出来。

在本节中,我们通过一个结构体,一个整数,一个信道和一个函数,建立了一个HTTP服务器, 这一切都是因为接口只是方法的集合,而几乎任何类型都能定义方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值