interface接口_golang 基础(Four) 接口进阶

 参考 Understanding Go Interface 

感谢 francesc 分享

接口

我们编程中少不了对接口使用和设计,无论你是使用哪种语言或多或少都会使用到**接口**。即使你说明重来没有显示定义过或者使用过接口,我想如果你也可能隐式地用到过接口。

今天我们就说一说 go 语言中的接口是如何设计的;如何使用的以及他与其他语言相比有什么自己的特点。

什么是 interface 

接口可以理解是规范、协议、用户使用手册和对类型抽象,对行为描述。说了这么一大堆还需要您自己了解。

> In object-oriented programming, a protocal or interface is a common means for unrelated objects to communicate with each other

 wikipedia

上面的话摘字 wiki,这里传递了两个重要的信息

- communicate 接口是用于通讯,接口就是用来定义通讯遵循的规则,类似一种协议。

- unrelated objects 耦合度低的对象,接口定义通讯规则可以使用两个互不相干的对象。通过接口会将关注点放在交流上,而不会关注与通讯无关的类型上。

乐高玩具就是一个好的例子。乐高玩具的一个piece 组合时只要遵守尺寸规则,无论大小和颜色就可以组合在一起进行拼接。

58848fb21753f2f9c7fb1d1a206c4ca4.png

以后兼职工作也是一样只要满足规定的条件,在拼接 Lego 玩具时是否可以拼接是和piece 的颜色和形状没有关系的,只要他们都遵守一定尺寸就可以进行拼接。在软件控制模块搭建和通讯也是通过定义一定接口规范来实现了。我想软件工程也在某些方面借鉴传统的行业。

想到 go 我们就会自然联想到他一个项目 docker,也就是 docker 项目让我们看 go 语言的商业化,docker 可以算上是 go 的最佳实践之一了。

之所以集装箱海运如何兴旺,因为提供一个统一接口,无论是什么样船或者是列车都可以可以运输集装箱,因为集装箱提供一种标准(接口)标准的尺寸和重量。

docker 设计也是源于集装箱设计,我们将 code 进行包装,然后提供一致接口这样我们的 code 就可以运行在任何符合其标准的环境了。

### 什么是 go 的 interface

在 go 语言中我们可以通过两个维度对类型进行划分

  • - 抽象类型

  • - 实体类型

当然在 go 语言中有很多种类型,不过我们大致可以将归为两种一种类型属于 abstract 类型(抽象类型)和 concrete 类型(实体类型)

实体类型

- 用于描述类型在内存中分配情况,根据类型我们就可以知道其在内存中存在形式。

int8/int16/int32/int64/struct/float

- 使用方法赋予数据一定的行为

type Number intfunc (n Number) Positive() bool{  return n >0}

6b96ec96ec602d34de9eadbb8dbd1c9e.png

抽象类型

这种类型就是接口,并不是说明类型的形状,而是告诉你能够使用这种类型来做什么。

抽象类型并没定义描述如何为这种类型分配内存空间,而是描述类型的行为。按行为为类型进行划分。这些抽象类型有 io.Reader 、 io.Writer 和 fmt.String 等等

type Positiver interface{  Positive() bool}

在 Positiver 接口中定义了 Positive 这里大家已经注意到了我们并没有指定接口的接收者(也就是实现者)。这就是 go 语言设计初衷,也就是我不关心你是什么类型只要你有一个形状(包括函数名、参数和返回值)一致的方法,我就认为你是实现了这个接口,你属于这个类型。

用来说明 go 语言接口的经典接口 Writer 和 Reader 接口

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

只要实现了接口的方法的类型就属于接口类型,所以集合是实体类型的集合。

35dee34253114bc2aa2d5ef287015e9b.png

接口的组合

type ReadWriter interface{  Read  Writer}

接口是可以组合,这个 ReadWriter 接口,当然实现这个接口类型应该是更强大而范围从图上来看却缩小了。但是接口越详细确定范围也就小。

interface{}

19d4dd632daee0231e9039eab1396ef3.png

这里有一个 interface{} , Rob Pike 指着 interface{} 是没有任何意思,因为没有任何限制,没有限制也就是没有意义,这个应该不难理解。在 go 语言中可以用 interface 表示类型,但是从 interface{} 来看我们是无法了解其代表类型的具体信息。

使用接口原因

  • - 编写通用的算法

  • - 隐藏实现的细节

  • - 提供拦截点

以上三点是我们使用接口原因,这里逐一给大家进行讲解。

a) func writeTo(f *os.File) error

b) func writeTo(w *os.ReadWriteCloser) error

c) func writeTo(w io.Writer) error

d) func writeTo(w interface{}) error

上面选择题您选择哪个?

答案是 b 或 c,a 选项问题是这里接收一个实体类型作为参数,这样对于测试 writeTo 就造成问题,我们每次测试这个方法就需要实例化一个 File 对象,这样得不偿失。

d 答案是对参数没有任何限制,从而也就是失去意义。没有任何关于类型有价值的信息,编译器也不会进行类型检查的。

b 和 c 根据你应用场景而定,接口的方法越少,复用性就越高。

> The bigger the interface, the weaker the abstraction

rob pike

> Be conservative in what you do, be liberal in what you accept from others

这是一条 robustness 理论,这句话源于 TCP 网络协议,在 TCP 网络协议是遵循发送到网络上数据是没有问题的,而从网络上接收数据即使有问题也是可以接受的。

### 抽象数据类型

数据类型的数学模型

通过下面依据来定义数据的行为

  • - 需要考虑数据可能的值

  • - 对此类型数据的可能操作

  • - 这些操作的行为

我们看一看栈这个抽象数据模型,有什么行为

top(push(x,s)) = x

我们 push 数据 x 入栈 s 然后用 top 方法进行出栈就会得到 x,逻辑性很强吧

pop(push(x,s)) = s

我们 push 数据 x 入栈 s 然后用 pop 弹出会返回栈进行push 前的 s。

empty(new())not empty(push(s,x))

通过上面分析我们就得到数据模型 stack

top(push(x,s)) = xpop(push(x,s)) = sempty(new())!empty(push(s,x))

这个比较抽象,我们通过定义一些行为来表述一种数据结构,理解起来可能会话费一段时间,可以通过行为表述抽象数据结构,例如先进先出就是数据结构。我们用行为表述了 stack 这一抽象数据。

接下来在 go 语言中,用具体代码对上面描述进行接口定义来表述 stack 抽象数据结构。

type Stack interface{  Push(v interface{}) Stack  Pop() Stack  Empty() bool}

在抽象数据结构上我们可以定义一些算法,这里 Size 算法需要传入抽象数据结构具有 Empty() 和 Pop() 行为,而且 Size 算法并不关系实现了这两个方法的类型的详细信息。这要实现了这两个方法即可。

func Size(s Stack) int {  if s.Empty(){    return 0  }  return Size(s.Pop()) + 1}

通过定义可排序的数据结构

type Interface interface{  Less(i,j int) bool  Swap(i, j int)  Len() int}

到现在大家可能有一些了解了在 go 语言中使用接口套路,我们先根据抽象数据结构可能有的操作,来推断出这些操作的行为。在这些抽象数据结构上定义一些通用算法,这些算法接收接口作为参数,只要这些类型提供其算法所需要的行为就行。看一看我们在 Writer 和 Reader 上的一些算法。

func Fprintln(w Writer, ar ...interface{}) (int,error)func Fscan(r Reader, a ...interface{}) (int, error)func Copy(w Writer, r Reader)(int, error)

我们可以在接口上写通用方法

现在我们明白之前说的那句话了吧,我们只是提供对的,对于接收的我们只是接收就好了。

这个选择题可以帮助你判断是否对上面所讲已经理解了。

  • a) func New() *os.File

  • b) func New() io.ReadWriteCloser

  • c) func New() io.Writer

  • d) func New() interface{}

首先我们来排除 d ,因为 interface{} 是没有任何意义和帮助的。b 和 c 也没有什么区别只是限制用户使用范围,如果只想让用户有写的能力就可以用 io.Writer 。最佳答案是 a,因为 robustness 原则是我们给出应该是好的确定的所以应该是 *os.File 实体类型而非仅是限制用户的接口。

那么我们终结一下 go 语言程序 robuness 的原则吧

返回给用户(这里用户是函数调用者)应该是确定的实体类型,而接收参数应该是接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值