阅读更多

0顶
1踩

编程语言

转载新闻 深入理解Go的interface

2017-06-14 15:14 by 副主编 jihong10102006 评论(0) 有14619人浏览
go
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
来自: Go中国
0
1
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Go语言基础、进阶、提高课程--第八节 深入理解interface1

    答案是否定的,Go 语言引入了一种新类型—Interface,它在效果上实现了类似于 C++的“多态”概念,虽然与 C++的多态在语法上并非完全对等,但至少在最

  • 深入理解golang接口interface

    在go语言中,interface有两种用法。 第一种是空接口,代表任意类型,空接口也可以理解为没有任何要求的接口类型,也可以说所有的类型都实现了空接口。 另一种是有方法的接口,在接口中定义一系列方法,一个类型如果...

  • 理解 Golang interface

    interface 是GO语言的基础特性之一。可以理解为一种类型的规范或者约定。它跟java,C# 不太一样,不需要显示说明实现了某个接口,它没有继承或子类或“implements”关键字,只是通过约定的形式,隐式的实现interface...

  • 深入理解Golang之interface

    interface:简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。 参考下面这个例子

  • (五)Go语言interface及并发

    目录interface什么是interfaceinterface类型interface值空interfaceinterface...Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,相信你会被interface的巧妙设计所折服。 什么是interf

  • go 语言 linux api,Go语言接口interface深入理解

    Go 语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。但是 Go 语言里有非常灵活的 接口 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为:如果谁能搞定这件事,...

  • go语言学习日记【三十三】golang中interface详解

    Go 语言里面设计最精妙的应该算 interface,它让面向对象,内容组织实现非常的方便, 当我们认真了解interface后,就会被 interface 的巧妙设计所折服。

  • 深入理解Go——interface

    文章目录Go接口iface和eface值接受者和指针接受者的区别interface实现多态 Go接口 非侵入式的接口 在Java中,派生类需要明确的声明它实现了某一个接口,并且需要实现借口中所有的函数。 而在Go中,采用的是非侵入...

  • Go interface深入分析

    interface是一种鸭子类型 无需显示声明,只要对象实现了接口声明的的全部方法,就实现了该接口 把对象的类型检查从编译时推迟到运行时 好处: 松耦合 可以先实现类型,再抽象接口 2.值receiv...

  • 深入理解 Go 语言中的接口(interface)

    在 Go 语言中接口(interface)是一种类型,一种抽象的类型接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节实现接口的条件一个对象只要全部实现了接口中的方法,那么就...

  • 深入理解Go语言接口

    本文介绍了Go语言中的接口概念、定义和实现方法。我们讨论了接口的特性,包括隐式实现、接口组合和空接口的使用。接着,我们探讨了定义接口的最佳实践,包括定义小接口、使用有意义的命名以及避免不必要的抽象。通过...

  • 重学Golang系列(一): 深入理解 interface和reflect

    interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface。本文即是对Go语言中的interface和reflect基础概念和用法的一次梳理,也算是我阶段学习的总结,以期温故而知新。 ...

  • 深入理解Go语言(01): interface源码分析

    深入理解Go语言(01): interface源码分析 分析接口的赋值,反射,断言的实现原理 版本:golang v1.12 interface底层使用2个struct表示的:eface和iface 一:接口类型分为2个# 1. 空接口# //比如 var i interface{} ...

  • 理解go interface看这一篇(实践检验真理)

    Go 语言中的接口是一组方法的签名,它是 Go 语言的重要组成部分。简单的说,interface是一组method签名的组合,我们通过interface来定义对象的一组行为。interface 是一种类型,定义如下: type Person interface { ...

  • 深入学习Go结构体struct/interface

    在平常的工作中长看到一些Go程序员使用interface,但实际并不是很了解,趁着假期学习一波。了解其中的使用原理,也可以提高自己阅读代码的能力和提高编写效率。 参考:...

  • golang--interface(接口)

    其他类型可以实现接口的方法,从而成为该接口的实例。...在Go语言中,其他类型可以实现接口的方法,从而成为该接口的实例。在代码中使用接口时,可以使用接口类型作为变量的类型、函数的参数类型或函数的返回类型。

  • Golang interface接口深入理解

    Golang interface接口深入理解 interface 介绍 如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键。在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有...

  • Go语言基础结构 —— Interface 接口

    但是除了在反射等使用场景中我们很难直接感知到接口的存在(虽然大多数人使用反射的时候也没有感知到接口在其中发挥的作用),但是想要深入理解Go语言,我们必须对接口有足够的了解.接下来我们将从接口的数据结构、...

  • 基于springboot大学生就业信息管理系统源码数据库文档.zip

    基于springboot大学生就业信息管理系统源码数据库文档.zip

Global site tag (gtag.js) - Google Analytics