结构体嵌套接口
今天看k8s client-go源码时发现,结构体嵌套接口的现象。
// deployments implements DeploymentInterface
type deployments struct {
client rest.Interface
ns string
}
之前的学习都是以下情况,
1 结构体嵌套结构体
type Address struct {
City string
State string
}
type Person struct {
Name string
Age int
Address // 结构体嵌套
}
通过组合的方式,增强了结构体的功能
2 结构体实现接口
package main
import "fmt"
type Writer interface {
Write(string)
}
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data string) {
fmt.Println("Writing:", data)
}
type Logger struct {
Writer // 接口嵌入
}
func main() {
logger := Logger{Writer: ConsoleWriter{}}
logger.Write("Hello, world!")
}
最常见的情况,相当了实现了一个接口的对象。类比java中的对象实现接口。
3 接口嵌套接口
type Name interface {
WriteName(name string)
}
type Number interface {
WriteNumber(number string)
}
type Person interface {
Name
Number
Hello()
}
type person struct {
name string
number string
}
func (p *person) WriteName(name string) {
p.name = name
}
func (p *person) WriteNumber(number string) {
p.number = number
}
func (p person) Hello() {
fmt.Println("hello")
}
func main() {
p := person{}
fmt.Println("Person:", p.name, p.number)
p.WriteName("DoveOne")
p.WriteNumber("1")
p.Hello()
fmt.Println("Person:", p.name, p.number)
}
增加了接口的方法集
结构体嵌套接口
看完以上三个例子(1 结构体嵌套结构体,2 结构体实现接口,3 接口嵌套接口),终于讲到本文的例子了。
例子1——匿名接口、非匿名接口使用上的区别
package main
import "fmt"
type HelloWorld interface {
Hello()
}
type Person1 struct {
Name string
Number string
HelloWorld //匿名字段
}
type Person2 struct {
Name string
Number string
tag HelloWorld //非匿名字段
}
type hello struct {
}
func (h hello) Hello() {
fmt.Println("hello")
}
func main() {
h := hello{}
p1 := &Person1{"DoveOne", "1", h}
p1.Hello()
p1.HelloWorld.Hello()
p2 := &Person2{"DoveOne", "1", h}
p2.tag.Hello() //非匿名字段必须指定字段名才能引用字段的方法
}
输出
hello
hello
hello
例子2——具有解耦优势
package main
import (
"fmt"
)
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// Array 实现Interface接口
type Array []int
func (arr Array) Len() int {
return len(arr)
}
func (arr Array) Less(i, j int) bool {
return arr[i] < arr[j]
}
func (arr Array) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
// 结构体嵌套接口——匿名接口(anonymous interface)
type reverse struct {
Interface
}
// 重写(override)
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
// 构造reverse Interface
func Reverse(data Interface) Interface {
return &reverse{data}
}
func main() {
arr := Array{1, 2, 3}
rarr := Reverse(arr)
fmt.Println(arr.Less(0,1))
fmt.Println(rarr.Less(0,1))
}
sort包中这么写的目的是为了重写Interface的Less方法,并有效利用了原始的Less方法;通过Reverse可以从Interface构造出一个反向的Interface。go语言利用组合的特性,寥寥几行代码就实现了重写。
对比一下传统的组合匿名结构体实现重写的写法,或许可以更好的帮助我们了解匿名接口的优点:
package main
import (
"fmt"
)
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type Array []int
func (arr Array) Len() int {
return len(arr)
}
func (arr Array) Less(i, j int) bool {
return arr[i] < arr[j]
}
func (arr Array) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
// 匿名struct
type reverse struct {
Array
}
// 重写
func (r reverse) Less(i, j int) bool {
return r.Array.Less(j, i)
}
// 构造reverse Interface
func Reverse(data Array) Interface {
return &reverse{data}
}
func main() {
arr := Array{1, 2, 3}
rarr := Reverse(arr)
fmt.Println(arr.Less(0, 1))
fmt.Println(rarr.Less(0, 1))
}
对比
上面这个例子使用了匿名结构体的写法,和之前匿名接口的写法实现了同样的重写功能,甚至非常相似。但是仔细对比一下你就会发现匿名接口的优点,匿名接口的方式不依赖具体实现,可以对任意实现了该接口的类型进行重写。这在写一些公共库时会非常有用,如果你经常看一些库的源码,匿名接口的写法应该会很眼熟。
采用传统的组合匿名结构体方式,代码中的reverse 结构体与Array耦合
type reverse struct { Array }
采用匿名接口(anonymous interface)的方式,可以对任意实现了该接口的类型进行重写
type reverse struct { Interface }
匿名接口还有一个作用就是对结构体添加一些约束,必须使用实现了该接口的类型来构造实例。结构体中可以包含一些其他的字段,而interface只有方法,没有field。
package main
import (
"fmt"
"reflect"
"sort"
)
type Array1 []int
func (arr Array1) Len() int {
return len(arr)
}
func (arr Array1) Less(i, j int) bool {
return arr[i] < arr[j]
}
func (arr Array1) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
type Array2 []int
func (arr Array2) Len() int {
return len(arr)
}
func (arr Array2) Less(i, j int) bool {
return arr[i] < arr[j]
}
func (arr Array2) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
type Sortable struct {
sort.Interface
// other field
Type string
}
func NewSortable(i sort.Interface) Sortable {
t := reflect.TypeOf(i).String()
return Sortable{
Interface: i,
Type: t,
}
}
func DoSomething(s Sortable) {
fmt.Println(s.Type)
fmt.Println(s.Len())
fmt.Println(s.Less(0, 1))
}
func main() {
arr1 := Array1{1, 2, 3}
arr2 := Array2{3, 2, 1, 0}
DoSomething(NewSortable(arr1))
DoSomething(NewSortable(arr2))
}
例子3——链式编程
回顾一下如何创建中间件,
func ListenAndServe(addr string, handler Handler) error
//handler填写nil时,为系统默认的DefaultServeMux
在http编程入门时,我们用过一个函数ListenAndServe,它的第一个参数填写端口,例如8080,第二个参数填写nil。但是第二个参数我们也可以不填写nil。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
第二个参数是一个接口,我们自己创建Handler 就需要实现ServeHTTP方法。
type MyMiddleware struct {
Next http.Handler
}
func (m *MyMiddleware ) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//在next handler之前做一些事件
m.Next.ServeHTTP(w, r)
//在next handler之后做一些事件
}
middleware/auth.go文件
type AuthMiddleware struct {
Next http.Handler
}
func (am *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if am.Next == nil {
am.Next = http.DefaultServeMux
}
auth := r.Header.Get("Authorization")
if auth != "" {
am.Next.ServeHTTP(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
}
AuthMiddleware 实现了ServeHTTP方法,AuthMiddleware 本身是一个Handler ,它里面又有一个Handler。所以相当于一个链式结构。
main.go文件
func main() {
http.HandleFunc("/companies", func(w http.ResponseWriter, r *http.Request) {
c := Company{
ID: 123,
Name: "Google",
Country: "USA",
}
enc := json.NewEncoder(w)
enc.Encode(c)
})
http.ListenAndServe(":8080", new(middleware.AuthMiddleware))
}
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Country string `json:"country"`
}
测试
test.http文件
### Without Auth
GET http://localhost:8080/companies HTTP/1.1
### With Auth
GET http://localhost:8080/companies HTTP/1.1
Authorization: abcd
总结
在Go语言中,结构体嵌入接口(embedding an interface in a struct)是一种将接口作为结构体字段的方式。但是,这里的“嵌入”与传统的字段嵌入稍有不同,因为接口本身不包含任何数据,只包含一组方法的签名。
当你在结构体中嵌入一个接口时,你实际上是在声明该结构体必须实现该接口的所有方法。这并不是说接口被“嵌入”到结构体中,而是说结构体承诺它将满足该接口的方法集。
参考资料
golang中结构体嵌套接口
Go语言结构体进阶
无风—interface 作为 struct field,谈谈 Golang 结构体中的匿名接口