go interface

Interface 底层有两种实现方式:

一种是不包含任何方法的空接口, 底层为 efface , 我们常将另外一个变量赋值给他, 把他当作“任意类型”来使用, 这种情况下他不包含任何方法。

源码如下 :

type iface struct { 

    _type  *_type

    data unsafe.Pointer     

}     

另一种是 包含方法的 iface ,他包含一组方法, 也可能包含其他接口, 我们常用它来做面向对象编程。

源码如下 :

type iface struct { 

    tab  *itab    

    data unsafe.Pointer     

}    

其中 data unsafe.Pointer 是指向底层数据的指针。 他们的不同点在于另外一个字段,

先看下 itab 。

type itab struct {   

    inter  *interfacetype  

    _type  *_type

    link   *itab  

    hash   uint32 // copy of _type.hash. Used for type switches.

    bad    bool   // type does not implement interface  

    inhash bool   // has this itab been added to hash?      

    unused [2]byte     

    fun    [1]uintptr // variable sized    

}

Itab 中包含了 efface 里面的  _type 字段!!! 那么我们可以认为其实 iface 里面包含了 eface 的属性, 是对 eface 的扩展。

再来看 _type , 他是描述 Go 语言中各种数据类型的结构体(描述data字段的类型),底层如下:

type _type struct {

    // 类型大小  

    size       uintptr

    ptrdata    uintptr      

    // 类型的 hash 值     

    hash       uint32     

    // 类型的 flag,和反射相关     

    tflag      tflag   

    // 内存对齐相关  

    align      uint8 

    fieldalign uint8     

    // 类型的编号,有bool, slice, struct 等等等等      

    kind       uint8 

    alg        *typeAlg  

    // gc 相关    

    gcdata    *byte  

    str       nameOff     

    ptrToThis typeOff 

}

再看 interfacetype , 他描述的是接口的类型。

type interfacetype struct { 

    typ     _type 

    pkgpath name  

    mhdr    []imethod 

}

他里面也包含了 _type 。

我们再用一张图来看下 iface 的全貌。

对比下 eface 的全貌。

根据上面的源码和图片,我们知道设计 efface 的目的可能就是为了在 interface 中不包含方法的时候节省内存, 而其他多余的字段基本上都是用来描述interface的方法的。

如下代码 :

package main

      

import "fmt" 

      

type Coder interface { 

    code()    

}     

      

type Gopher struct {    

    name string  

}     

      

func (g Gopher) code() {     

    fmt.Printf("%s is coding\n", g.name)  

}     

      

func main() { 

    var c Coder   

    fmt.Println(c == nil)                    // 输出 true

    fmt.Printf("c: %T, %v\n", c, c)         // 输出  c: <nil>, <nil>

      

    var g *Gopher      

    fmt.Println(g == nil)                   // 输出 true

      

    c = g     

    fmt.Println(c == nil)                    // 输出 false 

    fmt.Printf("c: %T, %v\n", c, c)         // 输出 c: *main.Gopher, <nil>

}

声明 c 的时候, c 的类型 和 底层数据都是 nil , 所以 c等于 nil 。

定义 g 的时候, g默认指向 nil , 所以 g 等于 nil 。

当将 g 赋值给 c 的时候, c 的 type 指向了 g , c 的底层数据也指向了 g 的底层数据(还是 nil) , 虽然打印出 c 的值还是 nil , 但是 c 却不等于 nil, 因为 c的 type 不等于 nil 。 其中 type 是接口的动态类型, 底层数据是接口的值。

interface 在编程的时候经常用来作为接口进行解耦。

比如我们的系统要实现存储, 我们的数据库可以选择 mysql、 mongodb等。 最简单的方法是我直接选择 mysql, 定义一个mysql对象, 这个对象实现我们存储需要的各种方法。

但是如果某天我们想把 mysql 换成 mongodb 呢? 这个时候我们有两种选择, 要么将原来的 mysql 对象改成 mongo对象, 这样调用存储的业务层不需要改动。要么我们创建一个新的 mongo对象, mysql对象保留下来, 我们修改业务层的调用为调用 mongo对象。

这两种方案都是可行的, 但是扩展的时候都不方便, 有没有更好的方法呢?

那就是使用接口。 先定义一个接口, 确定我们业务层需要实现的方法, 业务层直接通过这个接口进行调用。 然后我们再根据具体需求增加对应的存储对象(mysql、mongo等), 然后我们只需要在业务层调用之前先用我们需要的存储对象构造我们的接口就行了。 这样扩展、替换都非常方便。

这里面将把数据存储到数据库这个流程拆分成了两个独立的层。 数据存储只负责存储, 业务模块只负责业务逻辑。 

分层之后直接调用就是紧耦合的, 我们增加一层接口就是解耦, 业务层和存储层只需要遵循接口而不需要关注对方怎么实现的,这样就可以进行替换。

分层 - > 解耦 -> 替换

深度解密Go语言之关于 interface 的 10 个问题_梦醒人间-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值