深入理解Go的interface

栏目: Go · 发布时间: 8年前

内容简介:深入理解Go的interface
0. 引言

在 Golang 中,interface 是一个非常重要的概念和特性,之前写过两篇相关的文章:Golang “泛型编程”,谈一谈 Golang 的 interface 和 reflect。然后在 Gopher China 2017 的会上又有一个关于 interface 的 topic: understanding golang interface(Gopher China) — youtube,作者是 Francesc。故在此做一个整理和补充。

1. What is Interface?

引用

In object-oriented programming, a protocol or interface is a common means for unrelated objects) to communicate with each other. These are definitions of methods) and values which the objects agree upon in order to co-operate.   — wikipedia

这是 wikipedia 关于 protocal 的定义,将 interface 类比如 protocal 是一种非常助于理解的方式。protocol,中文一般叫做协议,比如网络传输中的 TCP 协议。protocol 可以认为是一种双方为了交流而做出的约定,interface 可以类比如此。

在 Golang 中,interface 是一种抽象类型,相对于抽象类型的是具体类型(concrete type):int,string。如下是 io 包里面的例子。

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
  Write(p []byte) (n int, err error)
}
// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
  Close() error
}

在 Golang 中,interface 是一组 method 的集合,是 duck-type programming 的一种体现。不关心属性(数据),只关心行为(方法)。具体使用中你可以自定义自己的 struct,并提供特定的 interface 里面的 method 就可以把它当成 interface 来使用。下面是一种 interface 的典型用法,定义函数的时候参数定义成 interface,调用函数的时候就可以做到非常的灵活。

type MyInterface interface{
  Print()
}
func TestFunc(x MyInterface) {}

type MyStruct struct {}
func (me *MyStruct) Print() {}

func main() {    
  var me Mystruct
  TestFunc(me)
}
2. Why Interface

Gopher China 上给出了下面三个理由:

  • writing generic algorithm (泛型编程)
  • hiding implementation detail (隐藏具体实现)
  • providing interception points (不知道如何翻译)
2.1 writing generic algorithm

严格来说,在 Golang 中并不支持泛型编程。在 C++ 等高级语言中使用泛型编程非常的简单,所以泛型编程一直是 Golang 诟病最多的地方。但是使用 interface 我们可以实现泛型编程,我这里简单说一下,具体可以参考我前面给出来的那篇文章。比如我们现在要写一个泛型算法,形参定义采用 interface 就可以了,以标准库的 sort 为例。

package sort
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package.  The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
  // Len is the number of elements in the collection.
  Len() int

  // Less reports whether the element with
  // index i should sort before the element with index j.
  Less(i, j int) bool

  // Swap swaps the elements with indexes i and j.
  Swap(i, j int)
}

...
// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
  // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
  n := data.Len()
  maxDepth := 0
  for i := n; i > 0; i >>= 1 {
    maxDepth++
  }
  maxDepth *= 2
  quickSort(data, 0, n, maxDepth)
}

Sort 函数的形参是一个 interface,包含了三个方法:Len(),Less(i,j int),Swap(i, j int)。使用的时候不管数组的元素类型是什么类型(int, float, string…),只要我们实现了这三个方法就可以使用 Sort 函数,这样就实现了“泛型编程”。有一点比较麻烦的是,我们需要将数组自定义一下。下面是一个例子。

type Person struct {
  Name stringAge  int
}
func (p Person) String() string {
  return fmt.Sprintf("%s: %d", p.Name, p.Age)
}
// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person 
//自定义
func (a ByAge) Len() int { 
  return len(a) 
}
func (a ByAge) Swap(i, j int) {
 a[i], a[j] = a[j], a[i]
}
func (a ByAge) Less(i, j int) bool {
 return a[i].Age < a[j].Age 
}
func main() {
  people := []Person{
   {"Bob", 31},
   {"John", 42},
   {"Michael", 17},
   {"Jenny", 26},
  }

  fmt.Println(people)
  sort.Sort(ByAge(people))
  fmt.Println(people)
}

另外 Fransesc 在 Gopher China 上还提到了一个比较有趣的东西和大家分享一下。在我们设计函数的时候,下面是一个比较好的准则。

Be conservative in what you send, be liberal in what you accept.    — Robustness Principle

对应到 Golang 就是:

Return concrete types, receive interfaces as parameter.   — Robustness Principle applied to Go

话说这么说,但是当我们翻阅 Golang 源码的时候,有些函数的返回值也是 interface。

2.2 hiding implement detail

隐藏具体实现,这个很好理解。比如我设计一个函数给你返回一个 interface,那么你只能通过 interface 里面的方法来做一些操作,但是内部的具体实现是完全不知道的。Francesc 举了个 context 的例子。 context 最先由 google 提供,现在已经纳入了标准库,而且在原有 context 的基础上增加了:cancelCtx,timerCtx,valueCtx。语言的表达有时候略显苍白无力,看一下 context 包的代码吧。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)    
    return &c, func() { c.cancel(true, Canceled) }
}

表明上 WithCancel 函数返回的还是一个 Context interface,但是这个 interface 的具体实现是 cancelCtx struct。

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {   
    return cancelCtx{
        Context: parent,
        done:    make(chan struct{}),
    }
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    Context

    done chan struct{} // closed by the first cancel call.

    mu       sync.Mutex
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call}func (c *cancelCtx) Done() <-chan struct{} {    return c.done
}
func (c *cancelCtx) Err() error {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.err
}
func (c *cancelCtx) String() string {
    return fmt.Sprintf("%v.WithCancel", c.Context)
}

尽管内不是实现上下面上个函数返回的具体 struct (都实现了 Context interface)不同,但是对于使用者来说是完全无感知的。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtx
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtx
func WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx
2.3 providing interception points

Francesc 这里的 interception 想表达的意思我理解应该是 wrapper 或者装饰器,他给出了一个例子如下:

type header struct {
  rt  http.RoundTripper
  v   map[string]string
}
func (h header) RoundTrip(r *http.Request) *http.Response {
  for k, v := range h.v {
    r.Header.Set(k,v)
  }
  return h.rt.RoundTrip(r)
}

通过 interface,我们可以通过类似这种方式实现 dynamic dispatch。

3. 非侵入式

Francesc 还提到 interface 的非侵入式特性。什么是侵入式呢?比如 Java 的 interface 实现需要显示的声明。

public class MyWriter implements io.Writer {}

这样就意味着如果要实现多个 interface 需要显示地写很多遍,同时 package 的依赖还需要进行管理。Dependency is evil。比如我要实现 io 包里面的 Reader,Writer,ReadWriter 接口,代码可以像下面这样写。

type MyIO struct {}
func (io *MyIO) Read(p []byte) (n int, err error) {...}
func (io *MyIO) Write(p []byte) (n int, err error) {...}
// io package
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
type ReadWriter interface {
    Reader
    Writer
}

这种写法真的很方便,而且不用去显示的 import io package,interface 底层实现的时候会动态的检测。这样也会引入一些问题:

  • 性能下降。使用 interface 作为函数参数,runtime 的时候会动态的确定行为。而使用 struct 作为参数,编译期间就可以确定了。
  • 不知道 struct 实现哪些 interface。这个问题可以使用 guru 工具来解决。

综上,Golang interface 的这种非侵入实现真的很难说它是好,还是坏。但是可以肯定的一点是,对开发人员来说代码写起来更简单了。

4. interface type assertion

interface 像其他类型转换的时候一般我们称作断言,举个例子。

func do(v interface{}) {
  n := v.(int)    // might panic
}

这样写的坏处在于:一旦断言失败,程序将会 panic。一种避免 panic 的写法是使用 type assertion。

func do(v interface{}) {
  n, ok := v.(int)  if !ok {  // 断言失败处理
  }
}

对于 interface 的操作可以使用 reflect 包来处理,关于 reflect 包的原理和使用可以参考我的文章。

5. 总结

interface 是 Golang 的一种重要的特性,但是这是以 runtime 为代价的,也就意味着性能的损失(关于 interface 的底层实现之后又时间再写)。抛开性能不谈(现实中使用 Golang 开发的程序 99% 性能都不是问题),interface 对于如何设计我们的代码确实给了一个很好的思考。

6. 参考

1. Golang "泛型编程"

2. 谈一谈 Golang 的 interface 和 reflect

3. understanding golang interface(Gopher China) — youtube

4. understanding golang interface(Gopher China) — slide

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Command Line

The Linux Command Line

William E. Shotts Jr. / No Starch Press, Incorporated / 2012-1-17 / USD 39.95

You've experienced the shiny, point-and-click surface of your Linux computer-now dive below and explore its depths with the power of the command line. The Linux Command Line takes you from your very ......一起来看看 《The Linux Command Line》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具