Interface

ace_kylin · · 2025 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# 接口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,这就解决了指针传入的问题

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2025 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传