Go接口 - interface 实践

interface是GO语言中非常重要的类型,它是用来定义一类方法集,只表示对象的行为(Behavior),GO语言的接口和实现不需要显式关联(也就是常说的duck类型),只要实现了接口所有方法,就可以当做该接口的一个实现,赋值给所有引用该接口的变量,从而满足面向对象编程(OOP)中的两个非常重要原则:依赖倒置、里氏替换。

也正由于这个特点,所以GO接口最佳的实践是:接口尽量的小,根据实际的需求定义的接口大小。

例如:io包体的Reader/Writer

type Reader interface {
   Read(p []byte) (n int, err error)
}
type Writer interface {
   Write(p []byte) (n int, err error)
}

更大的接口:net/http

type File interface {
   io.Closer
   io.Reader
   io.Seeker
   Readdir(count int) ([]fs.FileInfo, error)
   Stat() (fs.FileInfo, error)
}

善于合理运用interface,可以使你的代码简洁,更好的解耦,从而提高程序的扩展性,比如:在涉及到input/output设计的时候,引入io.Reader/io.Writer是一个不错的选择。

借用Steve Francia分享的静态网站生成器HUGO的例子:

Bad

func (page *Page) saveSourceAs(path) {
  b := new(bytes.Buffer)
  b.Write(page.Resource.Content)
  page.saveSource(b.Bytes(), path)
}
// by 数组需要通过bytes.NewReader转化后储存
func (p *Page) saveSource(by []byte, inPath string) {
  saveToDisk(inPath,bytes.NewReader(by))
}

Good

func (page *Page) saveSourceAs(path) {
  b := new(bytes.Buffer)
  b.Write(page.Resource.Content)
  page.saveSource(b, path)
}
// by 数组需要通过bytes.NewReader转化后储存
func (p *Page) saveSource(b io.Reader, inPath string) {
  saveToDisk(inPath, b)
}

注:saveSource方法中定义的接收参数是io.Reader,清晰简洁, 易扩展,该方法可以接收所有实现该接口的实例。

除了bytes.Buffer实现了Read方法

func (b *Buffer) Read(p []byte) (n int, err error) {
   b.lastRead = opInvalid
   if b.empty() {
      // Buffer is empty, reset to recover space.
      b.Reset()
      if len(p) == 0 {
         return 0, nil
      }
      return 0, io.EOF
   }
   n = copy(p, b.buf[b.off:])
   b.off += n
   if n > 0 {
      b.lastRead = opRead
   }
   return n, nil
}

还可以是Conn(http包),实现从网络读取数据:

type Conn interface {
   Read(b []byte) (n int, err error)

或是文件对象File(os包),从本地磁盘读取

func (f *File) Read(b []byte) (n int, err error) {
   if err := f.checkValid("read"); err != nil {
      return 0, err
   }
   n, e := f.read(b)
   return n, f.wrapErr("read", e)
}

另外,注意的是接口粒度以满足需求为准,不要有额外的方法,否则会导致依赖不清晰。

例如:我们在网络编程中经常会用到net包的Conn接口:

type Conn interface {
   Read(b []byte) (n int, err error)
   Write(b []byte) (n int, err error)
   Close() error
   LocalAddr() Addr
   RemoteAddr() Addr
   SetDeadline(t time.Time) error
   SetReadDeadline(t time.Time) error
   SetWriteDeadline(t time.Time) error
}

通常我们收到一个conn的时候,会开启一个协程读取数据:

Bad

func handleConn(c net.Conn) {

这里我们使用net.Conn,实际上只是read数据,不会调用Conn其他方法,用io.Reader接口就足够:

Good

func handleConn(r io.Reader)

如果还涉及关闭连接就用io.ReadCloser

Good

func handerConn(rc io.ReadCloser)

颗粒度小的接口可以清晰依赖的同时,也方便单测对接口进行mock,更好的聚焦于目标逻辑的测试。

那要在哪定义iterface呢?!

首先:一般会把接口定义放在需要使用该接口的package下,而不是实现包下,通常实现类返回的是结构体或是对应指针,这样实现类扩展新的方法就不需要修改对应的接口。同样以io包中的io.Reader/io.Wirter为例,接口定义在io包中,而他的实现bytes.Buffer、os.File等都是在不同包中。

其次:这就关于接口定义的时机。

一般来说是由依赖方驱动。

在当前模块有依赖外部服务的时候,这时候就会定义一个接口,来对外部的依赖资源进行抽象解耦,屏蔽接口的实现。相反,依赖的提供方在不知道使用方需求的时候,定义接口也就没什么意义。

我的博客Go接口 - interface 最佳实践 | 艺术码农的小栈

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值