Go面向接口篇
Go语言是面向接口的,面向对象只支持封装,其余继承多态靠接口来实现的。
接口
接口是什么?一种约定,一个抽象的类型。
接口作用?
注意:接口指定了类型应该具有的方法,类型决定了如何实现这些方法即接口由使用者定义。
接口定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了该接口。
定义接口
接口可被任意的对象实现,一个对象可以实现任意多个接口
任意的类型都实现了空interface(定义:interface{})即包含0个method的interface (表示任何类型:interface{} )
/* 定义接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } 接口变量:包括:实现者的类型、实现者的值 接口变量里面到底是值还是指针,可以选择。 /* 定义结构体 */ type struct_name struct { /* variables */ } //接口实现是隐式的 /* 实现接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法实现 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法实现*/ }
接口实现
func main() { var a animal var c cat a=c a.printInfo() //a cat //使用另外一个类型赋值 var d dog a=d a.printInfo() //a dog } type animal interface { printInfo() } type cat int type dog int func (c cat) printInfo(){ fmt.Println("a cat") } func (d dog) printInfo(){ fmt.Println("a dog") }
接口的值是一个两个字长度的数据结构,第一个字包含一个指向内部表结构的指针,这个内部表里存储的有实体类型
的信息以及相关联的方法集;第二个字包含的是一个指向存储的实体类型
值的指针。所以接口的值结构其实是两个指针,这也可以说明接口其实一个引用类型。
方法集
如果要实现一个接口,必须实现这个接口提供的所有方法。
实现接口有两种接收者?指针接收者、值接收者
实体类型以值接收者实现接口的时候,不管是实体类型的值,还是实体类型值的指针,都实现了该接口。
实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。
总结1:首先以方法接收者是值还是指针的角度看
Methods Receivers | Values |
---|---|
(t T) | T and *T |
(t *T) | *T |
如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有类型的指针能够实现对应的接口。
func main() { var c cat var d dog //值作为参数传递 invoke(c) //invoke(&c)也可以 //指针作为参数传递 invoke(&d) //必须是这个!!! } //需要一个animal接口作为参数 func invoke(a animal){ a.printInfo() } type animal interface { printInfo() } type cat int type dog int //值接收者实现animal接口 func (c cat) printInfo(){ fmt.Println("a cat") } //指针接收者实现animal接口 func (d *dog) printInfo(){ fmt.Println("a dog") }
结果:
a cat
a dog
例子:比如queue中目前只能支持int类型
//只支持int类型 注意这里举例只列出了Push实际操作记得将Pop也修改
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
//支持任何类型
type Queue []interface{}
func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
}
例子:1)如果只想支持int类型 2)例子:假如接口肚子都没限定类型,如何限定int
duck typing
并不是Go语言特有的
go属于结构化类型系统,类似duck typing(需要动态绑定)。这里假设Go结构duck typing
其他语言中的duck typing?
Python中的: def download(retriever): return retriever.get("www.baidu.com") 注意:运行时候才知道传入的retriever有没有get 通常需要注释来说明接口 C++中的:本身就支持type typing 通过模板来支持 template <class R> string download(const R& retriever) { return retriever.get("www.baidu.com") 注意:编译时才知道传入的retriever有没有get 需要注释来说明接口(因为敲代码时候不知道) Java中没有duck typing 类似代码是: <R extends Retriever> String download(R r){ return r.get("www.baidu.com"); } 注意:传入的参数必须实现Retriever接口 与Python与C++相比不需要注释说明 不是duck typing 问题:多个需求必须让R实现多个接口,这就有问题了
Go嵌入类型
一种可以把已有的类型声明在新的类型里的一种方式,其他语言中,继承做。
Go提倡的代码复用的方式是组合,用组合代替继承。
嵌入后,被嵌入的类型称之为内部类型、新定义的类型称之为外部类型,这里user
就是内部类型,而admin
是外部类型。
func main() { ad:=admin{user{"张三","zhangsan@flysnow.org"},"管理员"} fmt.Println("可以直接调用,名字为:",ad.name) fmt.Println("也可通过内部类型调用,名字为:",ad.user.name) fmt.Println("但是新增加的属性只能直接调用,级别为:",ad.level) ad.user.sayHello() ad.sayHello() //外部类型也可以声明同名的字段或者方法,来覆盖内部类型的 } type user struct { name string email string } type admin struct { user level string } func (u user) sayHello(){ fmt.Println("Hello,i am a user") } func (a admin) sayHello(){ fmt.Println("Hello,i am a admin") }
输出为:
可以直接调用,名字为: 张三
也可通过内部类型调用,名字为: 张三
但是新增加的属性只能直接调用,级别为: 管理员
Hello,i am a user
Hello,i am a admin
func main() { ad:=admin{user{"张三","zhangsan@flysnow.org"},"管理员"} sayHello(ad.user)//使用user作为参数 sayHello(ad)//使用admin作为参数 } type user struct { name string email string } //如果内部类型实现了某个接口,那么外部类型也被认为实现了这个接口 type Hello interface{ hello() } type admin struct { user level string } func (u user) hello(){ fmt.Println("Hello,i am a user") } func sayHello(h Hello){ h.hello() }
常用的嵌入组合?
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } type ReadWriter interface { Reader Writer } type ReadCloser interface { Reader Closer } type WriteCloser interface { Writer Closer }
目录结构以及代码
main.go
package main import ( "fmt" "time" "learngo/retriever/mock" "learngo/retriever/real" ) //方法改名只影响到使用者 type Retriever interface { Get(url string) string //不需要加func } type Poster interface { Post(url string, form map[string]string) string } const url = "http://www.imooc.com" //如果不加http://会报错 //使用者 func download(r Retriever) string { return r.Get(url) } func post(poster Poster) { poster.Post(url, map[string]string{ "name": "ccmouse", "course": "golang", }) } //组合接口 type RetrieverPoster interface { Retriever Poster } func session(s RetrieverPoster) string { s.Post(url, map[string]string{ "contents": "another faked imooc.com", }) return s.Get(url) } func main() { var r Retriever //必须要去实现 mockretriever //r 肚子里究竟是什么? 一个类型,一个值(指针,或者拷贝的真实值) mockRetriever := mock.Retriever{ Contents: "this is a fake imooc.com"} r = &mockRetriever //值接收者可以去掉&,但是也可以加上 inspect(r) //真实的Retriver 指针接收者必须要&地址 r = &real.Retriever{ UserAgent: "Mozilla/5.0", TimeOut: time.Minute, }
//方法1 inspect(r) //方法2 // Type assertion 通过r.(*内容)来获得 可能会出错,为了防止出错加ok if mockRetriever, ok := r.(*mock.Retriever); ok { fmt.Println(mockRetriever.Contents) } else { fmt.Println("r is not a mock retriever") } fmt.Println( "Try a session with mockRetriever") fmt.Println(session(&mockRetriever)) //这里如果改为&retriever也可以 } //看看r肚子里面是什么东西 func inspect(r Retriever) { fmt.Println("Inspecting", r) fmt.Printf(" > Type:%T Value:%v\n", r, r) fmt.Print(" > Type switch: ") switch v := r.(type) { case *mock.Retriever: fmt.Println("Contents:", v.Contents) case *real.Retriever: fmt.Println("UserAgent:", v.UserAgent) } fmt.Println() }
mockretriever.go
package mock import "fmt" //名字最好不要用mockRetriever这样会重复 type Retriever struct { Contents string } //常用系统接口 相当于其他语言的toString func (r *Retriever) String() string { return fmt.Sprintf( "Retriever: {Contents=%s}", r.Contents) } //实现者还想实现Post方法 指针接收者 func (r *Retriever) Post(url string, form map[string]string) string { r.Contents = form["contents"] return "ok" } //语言本身不需要说明 只需要实现Get方法即可 func (r *Retriever) Get(url string) string { return r.Contents }
retriever.go
package real import ( "net/http" "net/http/httputil" "time" ) //真实的Retriever type Retriever struct { UserAgent string TimeOut time.Duration //一个时间段 } func (r *Retriever) Get(url string) string { resp, err := http.Get(url) if err != nil { panic(err) } result, err := httputil.DumpResponse( resp, true) resp.Body.Close() //读完了之后要去close它 if err != nil { panic(err) } return string(result) }
输出
Inspecting Retriever: {Contents=this is a fake imooc.com} > Type:*mock.Retriever Value:Retriever: {Contents=this is a fake imooc.com} > Type switch: Contents: this is a fake imooc.com Inspecting &{Mozilla/5.0 1m0s} > Type:*real.Retriever Value:&{Mozilla/5.0 1m0s} > Type switch: UserAgent: Mozilla/5.0 r is not a mock retriever Try a session with mockRetriever another faked imooc.com Process finished with exit code 0
常用系统接口
Stringer 相当于其他语言的toString
type Stringer interface { String() string }
Reader 接口 Writer接口 :对文件的抽象 其实不一定是文件可以是其他的
type Reader interface { Read(p []byte) (n int , err error) }
type Writer interface {
Write(p []byte) (n int , err error)
}
接口的组合:
type ReadWriter interface { Reader Writer }