包装器模式 #
在基本模式的基础上,当返回值的类型与参数类型相同时,我们能得到下面形式的函数原型:
func YourWrapperFunc(param YourInterfaceType) YourInterfaceType
通过这个函数,我们可以实现对输入参数的类型的包装,并在不改变被包装类型(输入参数类型)的定义的情况下,返回具备新功能特性的、实现相同接口类型的新类型。这种接口应用模式我们叫它包装器模式,也叫装饰器模式。包装器多用于对输入数据的过滤、变换等操作。
下面就是 Go 标准库中一个典型的包装器模式的应用:
// $GOROOT/src/io/io.go
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
}
func (l *LimitedReader) Read(p []byte) (n int, err error) {
// ... ...
}
通过上面的代码,我们可以看到,通过 LimitReader 函数的包装后,我们得到了一个具有新功能特性的 io.Reader 接口的实现类型,也就是 LimitedReader。这个新类型在 Reader 的语义基础上实现了对读取字节个数的限制。
接下来我们再具体看 LimitReader 的一个使用示例:
func main() {
r := strings.NewReader("hello, gopher!\n")
lr := io.LimitReader(r, 4)
if _, err := io.Copy(os.Stdout, lr); err != nil {
log.Fatal(err)
}
}
运行这个示例,我们得到了这个结果:
hell
当采用经过 LimitReader 包装后返回的 io.Reader 去读取内容时,读到的是经过 LimitedReader 约束后的内容,也就是只读到了原字符串前面的 4 个字节:“hell”。
由于包装器模式下的包装函数(如上面的 LimitReader)的返回值类型与参数类型相同,因此我们可以将多个接受同一接口类型参数的包装函数组合成一条链来调用,形式是这样的:
YourWrapperFunc1(YourWrapperFunc2(YourWrapperFunc3(...)))
我们在上面示例的基础上自定义一个包装函数:CapReader,通过这个函数的包装,我们能得到一个可以将输入的数据转换为大写的 Reader 接口实现:
func CapReader(r io.Reader) io.Reader {
return &capitalizedReader{r: r}
}
type capitalizedReader struct {
r io.Reader
}
func (r *capitalizedReader) Read(p []byte) (int, error) {
n, err := r.r.Read(p)
if err != nil {
return 0, err
}
q := bytes.ToUpper(p)
for i, v := range q {
p[i] = v
}
return n, err
}
func main() {
r := strings.NewReader("hello, gopher!\n")
r1 := CapReader(io.LimitReader(r, 4))
if _, err := io.Copy(os.Stdout, r1); err != nil {
log.Fatal(err)
}
}
这里,我们将 CapReader 和 io.LimitReader 串在了一起形成一条调用链,这条调用链的功能变为:截取输入数据的前四个字节并将其转换为大写字母。这个示例的运行结果与我们预期功能也是一致的:
HELL