# 接口interface
golang是面向接口的编程语言,这是golang的语言特点之一。golang传统意义上的继承,多态等,它只支持封装。golang中的接口并不是传统(c/c++,java等)意义上的接口,而是传统面向对象中继承和多态在golang中是通过接口来完成的。在golang的interface出现之前,接口主要作为不同组建之间的契约存在的,对契约的实现是强制的,必须明确声明实现了这个接口。但是golang完全不同。多态是指代码可以根据类型的具体实现采用不同行为的能力,接口是一个或多个方法签名的集合,只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口,这称为StructuralTyping,接口只有方法声明,没有实现,没有数据字段接口可以匿名嵌入其它接口,或嵌入到结构中,将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针,只有当接口存储的类型和对象都为nil时,接口才等于nil,接口调用不会做receiver的自动转换,接口同样支持匿名字段方法,接口也可实现类似OOP中的多态,空接口可以作为任何类型数据的容器
eg:
```
type Traversal interface{
Traverse()
}
func main(){
traversal := getTraversal()
traversal.Traverse()
}
```
通过getTraversal()方法封装得到了一个traversal,这个traversal实现了Traversal这个interface
## duck typing
许多语言支持duck typing,包括c++,python,**java无法实现duck typing**。
duck typing的概念:像鸭子走路,像鸭子叫,长的像鸭子,那么它就是一只鸭子
duck typing的用途:描述事物的外部行为而非内部结构
在golang编程中就利用duck typing的方式进行,gopher不用在意调用方法的“类型”是一个什么,当gopher需要调用到某个方法时能提供这个方法即可,然后和这些方法进行互动
长得像鸭子就是鸭子,也就是说实现了interface就是实现了interface中定义的那个方法,那么实现该方法的struct就不用赋值给interface就拥有了这个能力。也就是说实现了这个struct的实例可以被fmt.Println调用。但是在定义的时候其实是interface实例被调用。只不过这个struct实现了这个接口,所以它也拥有了这个能力。
> 严格说来go属于结构化类型系统,类似duck typing,因为在定义duck typing时强调了“动态绑定”,但是golang是编译时就绑定了,从这个角度看,golang就不是duck typing
## golang接口的定义
```
graph LR
使用者-->实现者
```
golang中,一个类只需要实现接口要求的所有函数,我们就说这个类实现了这个接口
#### golang的非侵入式接口,看似做了很小的语法调整,实则影响很是深远:
1. go标准库,再也不需要绘制类库继承树图;
2. 实现类的时候,只需要关心自己应该实现哪些方法,不必纠结接口要拆得多细才合理,接口由使用方法按需定义,而不用事前规划;
3. 不用为了实现一个接口而导入一个包,因为多引用一个外部包,就意味着更多的耦合,接口使用方按自身需求定义,使用方无需关心是否有其他模块定义过类似的接口。
#### 代码示例,自主实现一个下载https://studygolang.com/网页的代码(其中含有多态的概念):
**main()**
```
package main
import (
"fmt"
"interfaceType/mock"
real2 "interfaceType/real"
)
type Link interface {
Get(url string) string
}
func download(l Link)string {
return l.Get("https://studygolang.com/")
}
func main() {
var l,m Link
l = mock.Retriever{"This is a fack link"}
fmt.Println(download(l))
m = new(mock.Retriever)
fmt.Println(download(m))
var r Link
r = real2.Retriever{}
fmt.Println(download(r))
}
```
**mockRetriever**
```
package mock
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
if r.Contents == ""{
return url
}
return r.Contents
}
```
**realRetriever**
```
package real
import (
"time"
"net/http"
"net/http/httputil"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r Retriever) Get(url string) string {
response, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(response, true)
if err != nil {
panic(err)
}
defer response.Body.Close()
return string(result)
}
```
#### 利用标准库实现下载一个下载https://studygolang.com/网页的代码:
```
package main
import (
"fmt"
"io"
"net/http"
"os"
)
// main is the entry point for the application.
func main() {
// Get a response from the web server.
r, err := http.Get("https://studygolang.com/")
if err != nil {
fmt.Println(err)
return
}
// Copies from the Body to Stdout.
io.Copy(os.Stdout, r.Body)
if err := r.Body.Close(); err != nil {
fmt.Println(err)
}
}
```
观察以上代码,不难发现,使用标准库实现网页的download更容易一些当然,我们这里要研究的东西是golang的接口,download的实现是次要的。
```
r, err := http.Get("https://studygolang.com/")
```
利用http.Get()函数得到一个http.Response类型的指针,观察一下http.Get()函数源码:
```
func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url)
}
```
在观察一下http.Response:
```
type Response struct {
Status string
StatusCode int
Proto string
ProtoMajor int
ProtoMinor int
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Uncompressed bool
Trailer Header
Request *Request
TLS *tls.ConnectionState
}
```
原来http.Response是一个结构,不难发现里面的Body是一个io.ReadCloser类型的,让我们再去看看io.ReadCloser的源码:
```
type ReadCloser interface {
Reader
Closer
}
```
原来Body是一个接口组合,实现了Reader和Closer方法
接下来继续看
```
io.Copy(os.Stdout, r.Body)
```
io.Copy()函数的源码:
```
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
```
第一个参数是一个Writer,第二个参数是一个Reader,当然,我们的Body实现了Reader这个接口,所以可以直接作为参数传入。
## 接口赋值
golang接口的赋值有以下两种:
1. 将对象实例赋值给接口:要求该对象实例实现了接口要求的所有方法
2. 将一个接口赋值给另外一个接口:
#### 将实例化对象赋值给接口
```
package main
import "fmt"
type myInt int
type LessAdd interface {
Less(b myInt) bool
Add(b myInt)
}
func main() {
var a myInt = 2
var b LessAdd = &a
b.Add(a)
fmt.Println(b.Less(0))
fmt.Printf("%d\n",a)
}
func (a myInt) Less(b myInt) bool {
return a < b
}
func (a *myInt) Add(b myInt) {
*a += b
}
```
> 有一个有趣的问题:a赋值给接口LessAdd时候,用的是地址的引用,而不是值的引用
```
var a myInt = 2
var b LessAdd = &a
```
接口LessAdd中包含了一个指针的方法func (a *myInt) Add(b myInt),在调用非指针方法func (a myInt) Less(b myInt) bool时候,golang会自动生成一个函数func(a *myInt)Less(b myInt)bool,这就解决了指针传入的问题
有疑问加站长微信联系(非本文作者))