golang学习笔记——结构体嵌套接口

结构体嵌套接口

今天看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 结构体中的匿名接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值