乐趣区

关于golang:如何用好-Go-interface

interface 是 Go 语言最精华的个性之一,始终以来想写一篇对于 interface 的文章,然而始终没敢写。继续几年之久,还是斗胆总结下。

Concrete types

struct 定义数据的内存布局。一些晚期倡议将办法蕴含在 struct 中,然而被放弃了。相同,办法如一般函数一样申明在类型之外。形容 (data) 和行为 (methods) 是独立且正交的。

一方面,办法只是一个带有“receiver”参数的函数。

type Point struct {x, y float}

func (p Point) Abs() float {return math.Sqrt(p.x*p.x + p.y*p.y)
}

func Abs(p Point) float {return math.Sqrt(p.x*p.x + p.y*p.y)
}

Abs 编写为一个惯例函数,性能没有变动。

什么时候应该应用办法,什么时候应该应用函数呢?如果办法不依赖类型的状态,则应该将其定义为函数。

另一方面,办法在定义其行为时,应用了类型的值时,与所附加的类型严密关联。办法能够从对应的类型中获取值,如果有指针“receiver”,还能够操纵其状态。

“类型”有时候很有用,有时候又很厌恶。因为类型是对底层内存布局的一个形象,会让代码关注于非业务逻辑上的货色,然而代码又须要在不同类型的数据间做解决。interface 就是其中一种泛型解决方案。

// Package sort provides primitives for sorting slices and user-defined collections.
package sort

// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int

    // Less reports whether the element with index i
    // must 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.
func Sort(data Interface) {...}

Abstract types

Go 的 interface 仅仅是函数的汇合,也定义了行为。interface 与类型之间没有显式的关系,类型也能够同时满足多个 interface 的要求。

type Abser interface {Abs() float
 }

 var a Abser
 a = Point{3, 4}
 print(a.Abs())
 a = Vector{1, 2, 3, 4}
 print(a.Abs())

Point 和 Vector 满足 Abser 的要求同时,也合乎 interface{} 的要求。不同的是,interface{} 没有任何行为(method)。

When & How

情理我都懂,然而何时应用,如何应用 interface 呢?

答案是,当不须要关怀实现细节的时候?

func fn(Parameter) Result

当函数编写者心愿暗藏实现细节时,应该把 Result 设定为 interface;当函数编写者心愿提供扩大点的时候,该当把 Parameter 设定为 interface;

暗藏实现细节

以 CancelCtx 为例:

type Context interface {Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}

type cancelCtx struct {...}

newCancelCtx 返回值为 cancelCtx。留神到 cancelCtx 是没有导出的,意味着使用者只能应用 Context 的变量来接管 newCancelCtx 返回值,从而达到暗藏实现的目标。cancelCtx 是否还有其余办法,以及具体如何实现,使用者并无感知。

提供扩大点

当咱们须要将文档长久化

type Document struct {...}

// Save writes the contents of the Document to the file f.
func (d *Document) Save(f *os.File) error

如果实现如上,Save 办法将 *os.File 作为写入的指标。然而此实现存在一些问题:

  1. 该实现排除了将数据写入网络地位的选项。假如网络存储成为需要,则此函数的签名必须更改,从而影响其所有调用者。
  2. 该实现很难测试。为了验证其操作,测试必须在写入文件后读取文件的内容。还必须确保 f 被写入到长期地位,并始终在之后删除。
  3. *os.File 裸露了许多与 Save 无关的办法,比方读取目录和查看门路是否为符号链接。

能够应用接口隔离准则从新定义该办法,优化实现为:

// Save writes the contents of d to the supplied ReadWriterCloser.
func (d *Document) Save(rwc io.ReadWriteCloser) error

然而,此办法依然违反繁多职责准则,它同时负责读取和验证写入的内容。将此局部责任拆分走,持续优化为:

// Save writes the contents of d to the supplied WriteCloser.
func (d *Document) Save(wc io.WriteCloser) error

然而,wc 会在什么状况下敞开。可能 Save 将无条件调用 Close,或者在胜利的状况下调用 Close,以上都不是一个好的抉择。因而再次优化

// WriteTo writes the contents of d to the supplied Writer.
func (d *Document) WriteTo(w io.Writer) error

接口申明了调用方须要的行为,而不是类型将提供的行为。行为的提供方具备高度的扩大空间,例如:装璜器模式扩大该行为。

type LogWriter struct {w  io.Writer}

func (l *LogWriter)Write(p []byte) (n int, err error) {fmt.Printf("write len:%v", len(p))
    return l.w.Write(r)
}

总结

对于 interface,很喜爱以下两句箴言:

Program to an‘interface’, not an‘implementation’—— GoF
Be conservative in what you do, be liberal in what you accept from others —— Robustness Principle

而不是

Return concrete types, receive interfaces as parameter
(由 cancelCtx 的例子可知,如果其类型是导出的 CancelCtx,返回 concrete types 与以上箴言是有出入的)

高级语言赋予了开发者高级的能力,让开发者不要关注具体值、类型,集中精力去解决业务逻辑(行为,method),interface 提供的就是这种能力。除了 interface,其余问题解决也是基于相似的思路:

Don’t just check errors, handle them gracefully
基于行为处理错误,而不是基于值或类型

本文作者 :cyningsun
本文地址 :https://www.cyningsun.com/08-…
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-ND 3.0 CN 许可协定。转载请注明出处!

退出移动版