返回顶部
扩大
缩小

人外有人天外有天

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 ReceiversValues
(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
}
View Code

目录结构以及代码

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
}

 

posted on 2018-07-24 18:57  笔记是研究的开始  阅读(764)  评论(0编辑  收藏  举报

导航