Go基础之面向接口

一.duck typing 的概念

  • 像鸭子走路,像鸭子叫,长得像鸭子,就是鸭子
  • 描述事物的外部行为而非内部结构
  • 严格说go属于结构化类型系统,类似duck typing ,但不是正真完全意义的duck typing, 因为go是编译绑定,而duck typing 要求动态绑定

     比较灵活,不管"retriever"是什么,只要满足"download"注释的要求,实现了get()方法,就可以拿来给"download"用 

 

     同样比较灵活,不管"retriever"是什么,只要满足"download"注释的要求,实现了get()方法,就可以拿来给"download"用  

 

      Java中,"downlod"传入的参数必须实现Retriever接口,这样强制性规定了"download"的参数必须实现一个什么样的模板,好处是可以不用为"download"写注释,而在写代码时编辑器就可以为代码检错甚至可以为其生成模板,但缺点是强制性规定了模板,失去了python和c++中的灵活性,也正因如此,它不是duck typing 

  • go语言中的duck typing :

  1. 具有python和c++中的灵活性
  2. 同时具有java中的类型检查,即不需要写注释

 

二.go中的接口 

  • 使用者(download())-------------> 实现者(retriever())
  • 与其他面向对象的语言不同,go中的接口是由使用者来定义的

接口的定义:

//定义一个接口Retriever,接口需要实现功能由使用者download决定的,这里是具有Get()方法
//接口里只有方法的声明,且只有方法,方法前不用加关键字func

type Retriever interface {
    Get(url string) string
}

func download(r Retriever, url string) string {
    return r.Get(url)
}

接口的实现

  • 接口的具体实现其名字不一定与定义的接口的名字一致,这里结构体"Retri"是对接口"Retriever"的具体实现,"Retri"实现了接口"Retriever"规定的Get(url string) string 方法

type Retri struct {  
}

func (r Retri) Get(url string) string{
    resp, err := http.Get(url)
    if err != nil {
        log.Panic(err)
    }
    
    result, err := httputil.DumpResponse(resp, false)  
    resp.Body.Close()  
    if err != nil {
        log.Panic(err)
    }
    
    return string(result)
}

验证:

func main(){
    r := Retri{}
    fmt.Println(download(r, "http://www.baidu.com"))

  • 当实现接口的函数的接受者是指针时,在创建接口变量时,必须是地址类型
  • 而当实现接口的函数的接受者值类型时,在创建接口变量时,接口变量可以是值类型也可以是地址
  • 接口变量为地址时,接口变量的类型和值分别是指针类型和地址

type Retri struct {  
}

func (r *Retri) Get(url string) string{
    resp, err := http.Get(url)
    if err != nil {
        log.Panic(err)
    }
    
    result, err := httputil.DumpResponse(resp, false)  
    resp.Body.Close()  
    if err != nil {
        log.Panic(err)
    }
    
    return string(result)
}

验证:

func main(){
    r := &Retri{}
    fmt.Println(download(r, "http://www.baidu.com"))

接口变量的类型和值 

  • go中的接口变量是有类型的,不用于其他语言接口实例只是一个简单的引用
  • 接口变量的类型并不是var后声明的类型,而是实现者的类型 接口变量的值是实现者的值(或者是 实现者的指针,指向一个实现者)
  • 实际应用中,不能对接口变量取地址,因为接口内部本身就可以含有一个指向实现者的指针

type Retri struct {
    value int
}

测试:
func main(){
    r := Retri{}
    fmt.Printf("%T, %v\n", r,r)  //输出结果: main.Retri, {0} 这里接口变量 r 的类型是实现者类型,值是真实的值,是结构体中

              值的一份拷贝
}

func main(){
    r := &Retri{}
    fmt.Printf("%T, %v\n", r,r)  //输出结果: *main.Retri, &{0} 这里的接口变量 r 的类型是实现着指针,值是一个真实值的地址,

              是对结构体中值的一个引用
}

接口类型的判断方法 :( 除了用 fmt.Printf("%T, %v\n", r,r) 之外 )

1> Type Switch     

func main(){
    var r Retriever = Retri{1}       

//注意,这里若直接用 "r := Retri{1}  "进行Type Switch  编译器会认为"r"不是Retriever接口,所以需要强转一下r := Retriever(Retri{1})

//这里,r 的类型并不是var声明的接口类型"Retriever",而是实现者的类型"Retri"


    switch v := r.(type) {
        case Retri :
            fmt.Println("value:", v.value)  //结果:value: 1 说明"r"是实现了"Retriever"接口的Retri类型
    }

2> Type Assertion 

func main(){
    r := Retriever(Retri{1})
    
    if retri, ok := r.(Retri); ok{
        fmt.Println(retri.value)    //结果:1 说明"r"是实现了"Retriever"接口的Retri类型
    }    
}

空接口:interface{}

  • golang里的所有类型都实现了空接口interface{},它可以表示任何类型 通常将它作为一个函数的参数或者结构体的字段,以实现对类型的抽象
  • 接口向普通类型转换成为"类型断言(Type Assertion)",    如,var a interface{}    a.(int) 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

interface{}可用于向函数传递任意类型的变量,但对于函数内部,该变量仍然为interface{}类型(空接口类型)

package main

import "fmt"

/*

**用于输出数组元素

*/

func echoArray(a interface{}){

  for _,v:=range a{

    fmt.Print(v," ")

  }

  fmt.Println()

  return

}

func main(){

  a:=[]int{2,1,3,5,4}

  echoArray(a)

}

//以上代码将会报错,因为对于echoArray()而言,a是interface{}类型,而不是[]int类型

所以前面代码中,将echoArray()做如下修改即可:

1

2

3

4

5

6

7

8

func echoArray(a interface{}){

    b,_:=a.([]int)//通过断言实现类型转换

  for _,v:=range b{

    fmt.Print(v," ")

  }

  fmt.Println()

  return

} 

注意:在使用断言时最好用 

1

2

3

4

b,ok:=a.([]int)

if ok{

    ...

}

 接口的组合:

type Retriever interface {
    Get(url string) string
}

type Poster interface {
    Post(url string, form map[string] string) string
}

// 接口"RetrieverPoster"是接口"Retriever"和"Poster"的组合

type RetrieverPoster interface {
    Retriever
    Poster
}

 

具体实现者:"Retri"

type Retri struct{
    Contents string
}

func (r Retri) Get(url string) string{   
    return "Get:"+url+"  contents: "+r.Contents
}

func (r Retri) Post(url string, form map[string]string) string{
    r.Contents = form["name"]
    return "Post ok"    
}

  "Retri"即实现了接口"Retriever"的Get(), 又实现了接口"Poster"的Post(), 如此,它既可以赋给"Retriever"接口变量,又可以赋给"Poster"接口变量,还可以赋给"RetrieverPoster"组合接口变量

func download (r Retriever) string {   
    return r.Get("http://www.imook.com")   //Get()的具体实现是:返回Get()的输入和Retri.Contents的值
}

func post (p Poster) string{       //Post()的具体实现是:修改Retri.Contents的值并返回"Post ok"
    return p.Post("http://www.imook.com", map[string]string{"name": "xiaoming",
    "course": "math"})
}

func session (s RetrieverPoster) string{
    s.Post("http://www.baidu.com",  map[string]string{"name": "hahaha"})
    return s.Get("http://www.baidu.com")
}

func main() {
    
    var r1 Retriever = reality.Retri{"r1"}   
    var r2 Poster = reality.Retri{"r2"}   
    var r3 RetrieverPoster = reality.Retri{"r3"}
    
    fmt.Println(download(r1))
    fmt.Println(post(r2))
    fmt.Println(session(r3))    
}

结果:

Get:http://www.imook.com  contents: r1
Post ok
Get:http://www.baidu.com  contents: r3

  注意, session(r3),先调用Post()对初始化的reality.Retri{"r3"}做修改,在调用Get()返回reality.Retri.Contents,此时,返回的仍然是初始值"r3",而不是"hahaha",原因是,接口实现方法的接受者是值传递,传递的是一份拷贝,需做如下修改 

type Retri struct{
    Contents string
}

func (r *Retri) Get(url string) string{
    
    return "Get:"+url+"  contents: "+r.Contents
}

func (r *Retri) Post(url string, form map[string]string) string{
    r.Contents = form["name"]
    return "Post ok"
    
} func main() {
    
    var r1 Retriever = &reality.Retri{"r1"}    
    var r2 Poster = &reality.Retri{"r2"}    
    var r3 RetrieverPoster = &reality.Retri{"r3"}
    
    fmt.Println(download(r1))
    fmt.Println(post(r2))
    fmt.Println(session(r3))    
}

结果:Get:http://www.imook.com  contents: r1
Post ok
Get:http://www.baidu.com  contents: hahaha

常用的系统接口:

1> Stringer  任何类型只要实现了Stringer接口,都可以用print()对其进行String()函数内自定义的输出类型

type Stringer interface {
    String() string

type S struct{
    value string
}

func (s S) String() string {
    return fmt.Sprintf("[content=%s]", s.value)
}


func main(){
    s := S{"Hello!"}
    fmt.Println(s)     //输出结果:[content=Hello!]
}

2> Reader 和 Writer  它们是对文件(file)类型的一个抽象

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)

//  os.file 对Reader 和 Writer的实现

func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)

func (f *File) Write(b []byte) (n int, err error) {
    if err := f.checkValid("write"); err != nil {
        return 0, err
    }
    n, e := f.write(b)
    if n < 0 {
        n = 0
    }
    if n != len(b) {
        err = io.ErrShortWrite
    }

    epipecheck(f, e)

    if e != nil {
        err = f.wrapErr("write", e)
    }

    return n, err
}
 

三.type和interface 

   

  在go中,type很好地实现了一对多,也就是利用这个type关键字可以定义出多种多样的不同类型。而interface很好地支持了多对一,即任何类型都可以被interface类型接收。

  Go语言里可以使用type关键字来把一个类型来转换成另外一个类型而保持数据的本质不变

  go中可以通过type定义出基于同一原始类型的不同的类型,再根据这些类型分别定义相应的方法。这样不同的类型就分别实现了不同的接口,虽然这些类型的原始数据可能是一样的。这也体现了面向对象编程的思想。

参考来源:关于go接口的一些说明

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值