golang中的结构体匿名成员

最近在开发caddy的插件, 碰到了很多以前不怎么注意的地方, 先重点记录下几个点.

写插件比较追求的一点是: 如何不破坏原有的体系而达到想要的效果. 这个确实有点难…

结构体匿名成员

有关结构体匿名成员在《Effective Go》中的embedding一节讲的很详细, 一言一蔽之: 另一种形式的继承. 如果B类型中包含了A类型的匿名成员, 则B类型的实例拥有(继承)了所有A类型实现的方法.

看个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import "fmt"

type I interface {
    Say()
    Run()
}

type XXX struct {
    Name string
}

func (x XXX) Say() {
    fmt.Printf("XXX name is: %s\n", x.Name)
}

func (x XXX) Run() {
    fmt.Printf("XXX run away\n")
}

type YYY struct {
    XXX
    Age int
}

func (y YYY) Say() {
    fmt.Printf("YYY name is: %s, age is: %d\n", y.Name, y.Age)
}

func Play(p I) {
    p.Say()
    p.Run()

    _, ok := p.(XXX)
    if !ok {
        fmt.Printf("not xxx\n")
    } else {
        fmt.Printf("is xxx\n")
    }
}

func main() {
    x := &XXX{"x-aka-x"}
    y := &YYY{XXX: x, Age: 23}

    fmt.Printf("Play x\n")
    Play(x)
    fmt.Printf("Play y\n")
    Play(y)
}

这个小程序运行后会输出:

1
2
3
4
5
6
7
8
=== Play x
XXX name is: x-aka-x
XXX run away
is xxx
=== Play y
YYY name is: x-aka-x, age is: 23
XXX run away
not xxx

利用这个特性我们可以很方便的在已有的实现上面实现我们个性化需求. 比如我们想在Play函数中调用Say()之前执行一些我们的逻辑, 我们可以很方便的通过写一个ZZZ类来继承YYY, 然后改写Say()方法来实现. 如果所有实现都根据接口来, 那没有任何问题, 但是总会碰到例外.

type assertion

上面的Play()函数中有这么一句_, ok := p.(XXX), 这个是将p尝试转换成XXX类型. 如果一个函数依赖特定类型进行处理, 那各个组建之间协调起来会比较麻烦. 我在caddy里面就遇到了类似的问题. caddy里面的插件是通过链式调用下一个模块的ServeHTTP(w http.ResponseWriter, r *http.Request)方法来实现的. 如果你的模块想在http响应头里面加一些自己的内容, 很容易联想到去实现http.ResponseWriter的接口, 然后我们可以直接继承caddy中的responseWriterWrapper类, 然后重写对应的WriteHeader(status int)方法. 这里有一个问题, 如果另外有个模块需要处理特定类型的w, 那么一旦你改变了对应的w, 则后续模块的功能都无法生效了. 原生的header插件与proxy插件就存在这么一个小问题.

这个最好的解决办法是原生支持这么一个hook~

最后

推荐一篇文章《Golang中的面向对象继承》