初识接口:为什么我们需要接口?
当我们写一个获取网页内容的程序时,我们可能写成这样,满满的C-Style面向过程写法。main()和retrieve()的关联本该不大,但如此写, 耦合 性却非常高,main()函数必须要调用retrieve()。func retrieve(url string) string{ resp,err := http.Get(url) if err!= nil{ panic(err) } defer resp.Body.Close() bytes,_:= ioutil.ReadAll(resp.Body) return string(bytes)}func main(){ fmt.Println(retrieve("https://www.douban.com"))}
如果retrieve模块的实现是由另一个团队编写,或者需要替换成测试团队的代码时,高耦合会使之成为一场灾难。我们所需要的不过是一个东西,它能提供一个方法来获取网页信息,翻译到Golang程序中应该是这样:
type retriever interface { Get(string) string}func getRetriever() retriever { r := util.Retriever{} return r}func main() { r := getRetriever() fmt.Println(r.Get("http://www.sina.com"))}
如此一来,我们只要实现一个带有
Get(
string
)
string的方法struct,就可以低耦合地实现原有功能,让main函数不再和唯一的retrieve方法绑定。与java不同,Go中的struct并不需要生命实现了哪个接口,只要实现对应方法,就可是该类型,这是Duck Typing的概念,接口实现如下:
type Retriever struct {}func (Retriever) Get (url string) string{ resp,err := http.Get(url) if err!= nil{ panic(err) } defer resp.Body.Close() bytes,_:= ioutil.ReadAll(resp.Body) return string(bytes)}
鸭子类型
可达鸭是鸭子吗?我们如何定义鸭子?
如果从传统的面向对象(OOP)
的角度说:鸭子属于鸟纲雁形目、脊椎动物亚门,脊索动物门,它是一层一层继承而来。可达鸭显然不是一只鸭子。但按照duck typing的定义方法,“走路像鸭子,叫声像鸭子,那么就是鸭子”。
duck typing描述事务的外部行为而非内部结构。而duck typing的标准并不唯一,如果从可以吃的角度来看,可达鸭又不能算作鸭子,
duck typing是从使用者角度定义的。在Go编程中,我们要和一个东西进行互动,并不关心它是个什么东西,只要它能提供相应的能力就可以。我们先来看看别的语言的duck typing:在python中,我们这样写鸭子类型:
def download(retriever): return retriever.get("www.douban.com:)
运行时才知道传入的retriever有没有get()
需要注释来说明接口
C++中的duck typing
template <class R>string download(const R& retriever){ return retriever.get("www.douban.com");}
编译时才知道传入的retriever有没有get
需要注释来说明
Java:
<R extends Retriever>String download(R r){ return r.get("www.douban.com")}
传入的参数必须实现Retriever接口,非常不灵活
不是duck typing
Go语言的duck typing
Go中的duck typing定义:
// 定义接口type Retriever interface { Get(url string) string}func Download(r Retriever) string{ return r.Get("http://www.douban.com")}
Go中duck typing的实现:
type Retriever struct { Contents string}func (r Retriever) Get(url string) string { return r.Contents}
接口的实现是隐式的
只要实现接口里的方法就可以
接口的值类型
Golang接口的内容并不是一个指针,而是包含了接口的类型和接口的值,使用下面的程序进行测试:
type Retriever interface { Get(url string) string}type Retriever struct { UserAgent string TimeOut time.Duration}r := Retriever{}fmt.Printf("%T %v",r,r)
得到如下结果:
real.Retriever { 0s}
我们还可以使用switch来判断接口类型:
func inspect(r retriever.Retriever){ fmt.Printf("%T %v\n",r,r) switch r.(type) { case mock.Retriever: fmt.Println("This is mock Retriever") case real.Retriever: fmt.Println("This is real Retriever") }}
接口的组合
我们可以通过一下的形式对接口进行组合:
type Retriever interface { Get(url string) string}type Poster interface { Post(url string) string}type Obj interface { Retriever() Poster() Option() }
Go语言标准接口
go语言中还有诸如Stringer、Reader、Writer等有用的interface{},这样可以和go的基础函数进行交互。