美文网首页
intrface实现分析

intrface实现分析

作者: 我给你一个大大的YES | 来源:发表于2019-12-04 17:56 被阅读0次

    前言

    关于interface

    接口(interface)代表一种“约定”或“协议”,是多个方法声明的集合。允许在非显示关联情况下,组合并调用其它类型的方法。接口无需依赖类型,带来的优点就是减少调用者可视化方法,隐藏类型内部结构和具体方法实现细节。虽然接口的优点有很多,但是接口的实现是在运行期实现的,所以存在其它额外的开销。在日常开发过程中是否选择接口需要根据场景进行合理的选择。

    关于k8s源码阅读interface

    源码中出现大量interface,并每个接口方法都有多个实现方法,所以进行学习记录,了解k8s源码是如何进行多态开发的,下面直接进入代码实验,之后进入interface内部实现

    代码实验

    项目结构如下

    hello/
    ├── main.go //main
    ├── user3.go //interface的实现方法
    └── userinfo
        └── userInfo.go //interface
    

    interface如下

    //file:userinfo/userInfo.go
    //模仿项目的service,将接口另起包
    package userinfo
    
    //UserInfo for user
    //UserInfo及GetName等需要外部使用,注意首字母要大写
    type UserInfo interface {
        GetName(name string) string
        GetAge(age int) int
    }
    

    循序渐进

    user1调(u1 *user1)GetName

    graph LR
    user1-->B["(u1 *user1)GetName"]
    
    //file:main.go
    package main
    
    import (
        "fmt"
    
        "k8s.io/hello/userinfo"
    )
    
    type user1 struct {
        u userinfo.UserInfo
    }
    //func(t *T) B(){}方式,按值调用实现接口会报错,后面会说明
    func (u1 *user1) GetName(name string) string {
        return name + "user1"
    }
    
    func (u1 user1) GetAge(age int) int {
        return age
    }
    
    func main() {
        //也可以直接这样声明 var u1i userinfo.UserInfo = &user1{}
        u1 := new(user1)
        var u1i userinfo.UserInfo = u1
        
        fmt.Println("user1调(u1 *user1)GetName  <====>  " + u1i.GetName("user1"))
    }
    

    输出

    image.png

    user2调(u1 *user1)GetName

    graph LR
    user2-->B["(u1 *user1)GetName"]
    
    //file:main.go
    package main
    
    import (
        "fmt"
    
        "k8s.io/hello/userinfo"
    )
    
    type user1 struct {
        u userinfo.UserInfo
    }
    
    func (u1 *user1) GetName(name string) string {
        return name + "user1"
    }
    
    func (u1 *user1) GetAge(age int) int {
        return age
    }
    
    func main() {
        var u1i userinfo.UserInfo = &user1{}
        u2 := &user2{u: u1i}
    
        fmt.Println("user2调(u2 *user2)GetName  <====>  " + u2.u.GetName("user2"))
        fmt.Println("user1调(u1 *user1)GetName  <====>  " + u1i.GetName("user1"))
    }
    

    输出

    image.png

    k8s源码中(例如kubelet创建Pod源码)就是按照上面将接口的实现方法对象地址,赋予要调要该方法的对象,不过在代码中有一些是这样实现的

    user2调(u3 *user3)GetName

    graph LR
    user2-->B["(u3 *user3)GetName"]
    
    //file: user3.go
    //k8s源码会像如此,将一个package分成多个文件实现
    package main
    
    import (
        "k8s.io/hello/userinfo"
    )
    
    type user3 struct {
        u userinfo.UserInfo
    }
    
    //返回&user3{u: userin}
    func newu3in(userin userinfo.UserInfo) userinfo.UserInfo {
        return &user3{u: userin}
    }
    
    func (u3 *user3) GetName(name string) string {
        return name
    }
    
    func (u3 *user3) GetAge(age int) int {
        return age
    }
    
    

    k8s中一处代码就是如此实现的

    //file: k8s.io/kubernetes/pkg/kubelet/kuberuntime/instrumented_services.go--29行
    type instrumentedRuntimeService struct {
        service internalapi.RuntimeService
    }
    
    // Creates an instrumented RuntimeInterface from an existing RuntimeService.
    func newInstrumentedRuntimeService(service internalapi.RuntimeService) internalapi.RuntimeService {
        return &instrumentedRuntimeService{service: service}
    }
    

    回到具体调用

    package main
    
    import (
        "fmt"
    
        "k8s.io/hello/userinfo"
    )
    
    type user1 struct {
        u userinfo.UserInfo
    }
    
    func (u1 *user1) GetName(name string) string {
        return name
    }
    
    func (u1 *user1) GetAge(age int) int {
        return age
    }
    
    type user2 struct {
        u userinfo.UserInfo
    }
    
    func main() {
    
        var u1i userinfo.UserInfo = &user1{}
        u2 := &user2{u: u1i}
        
        u3 := new(user3)
        u2u3 := user2{u: newu3in(u3)}
        
        //或下面方式均可实现
        //var u3i userinfo.UserInfo = &user3{}
        //u2u3 := user2{u: newu3in(u3i)}
    
        fmt.Println("user2调(u2 *user2)GetName  <====>  " + u2.u.GetName("user2"))
        fmt.Println("user1调(u1 *user1)GetName  <====>  " + u1i.GetName("user1"))
        fmt.Println("user2调(u3 *user3)GetName  <====>  " + u2u3.u.GetName("user3"))
        
    }
    

    输出

    image.png

    1.interface内部实现

    // 接口内包含有方法的实现
    type iface struct {
        tab  *itab
        data unsafe.Pointer     // 实际对象指针
    }
    
    // 类型信息
    type itab struct {
        inter *interfacetype    // 接口类型
        _type *_type            // 实际类型对象
        fun   [1]uintptr        // 实际对象方法地址
    }
    
    // 接口内不包含方法的实现,即nil interface.
    type eface struct {
        _type *_type
        data  unsafe.Pointer
    }
    

    1.1. 按值实现接口和按指针实现接口区别

    1.1.1. 按值实现接口

    type T struct {}
    type Ter interface{
        A()
        B()
    }
    
    func(t T) A(){}
    func(t *T) B(){}
    
    var o T
    var i Ter = o //取值
    

    当将o实现接口Ter时,其实是将T类型内存拷贝一份,然后i.data指向新生成复制品的内存地址。当调用i.A()方法时,经过以下3个步骤:

    graph LR
    A["A. 通过i.(*data)变量获取复制品内的内容"]-->B["B. 获取i.(*data).A内存。"]
    B-->C[" C. 调用i.(*data).A()方法"]
    
    • A. 通过i.(*data)变量获取复制品内的内容。
    • B. 获取i.(*data).A内存。
    • C. 调用i.(*data).A()方法。
    image.png

    当调用i.B()方法时,由于receiver的是T.B()和T.A()是不一样的,调用经过也存在区别:*

    graph LR
    A["A. 通过i.(*data)变量获取其内容。"]-->B["B.Go内部实现禁止对该复制品进行取地址操作"]
    
    • A. 通过i.(*data)变量获取其内容(此时的内容指向类型T的指针)。
    • B. 由于i.(*data)变量获取的内容是地址,所以需要进行取地址操作。但Go内部实现禁止对该复制品进行取地址操作,所以无法调用i.B()方法。


      image.png

    ==⚠️所以代码进行编译时会报错==T does not implement Ter (B method has pointer receiver)

    1.1.2. 按指针实现接口

    type T struct {}
    type Ter interface{
        A()
        B()
    }
    
    func(t T) A(){}
    func(t *T) B(){}
    
    var o T
    var i Ter = &o //取址
    

    此时通过调用i.A()和i.B()方法:

    graph LR
    A["A. 通过i.(*data)变量获取复制品内容"]-->B["B. 获取T类型地址"]
    B-->C["C. 调用类型T的A和B方法"]
    
    • A. 通过i.(*data)变量获取复制品内容(此时内容为指向类型T的指针)。
    • B. 获取复制品内容(即T类型地址),
    • C. 然后调用类型T的A和B方法。

    1.1.3. 接口方法集合

    通过以上对接口实现分析,可以得出接口的方法集是:

    1. 类型T的方法集包含所有receiver T方法。

    2. 类型*T的方法集合包含所有Receiver T + *T方法。

    相关文章

      网友评论

          本文标题:intrface实现分析

          本文链接:https://www.haomeiwen.com/subject/zzjlgctx.html