接口

1.duck typing

  • 描述事物的外部行为而非内部结构
  • 严格来说,go属于结构化类型系统,类似duck typing,我们就当做go语言是duck typing

2. go语言的接口

 使用者 ------------------> 实现者
download                  retriever

实现一个download方法,传入retriever实现者,调用download,让传入的retriever去实现下载,然后返回
从上面的描述可以看出:

  • 接口 由 使用者 定义

接口的定义和使用 & 如何理解”接口由使用者定义“这句话?

  • 举例:
package main
import (
	"fmt"
)

// 首先定义一个接口
type Recipient interface {
  Get() string // 接口里面就是Get方法,不需要写func,因为都是方法
}

// 然后使用者调用这个接口
func download(r Recipient) string {
  return r.Get()
}

// 用一种类型去实现这个接口,比如用结构体去实现
type RecipientStruct struct{
  Content string
}

func (r RecipientStruct) Get() string {
  return r.Content
}

func main() {
  // 使用接口
  // 第一步: 创建一个接口
  var r Recipient
  // 第二步: 将类型(这里是结构体)赋值给接口
  r = RecipientStruct{Content: "http: //www.qq.com"}
  // 第三步: 调用使用者函数
  fmt.Println(download(r))
}

接口内部

  • 接口内部是一个类型和一个值(或指针)
  • 举例:
    • 下面的代码,最红打印出的是realRetriever.Retriever {Mozilla/5.0 1m0s},可以看出,有两个内容:
      • 一个是类型realRetriever.Retriever
      • 一个是值{Mozilla/5.0 1m0s}
// 目录结构
- real
    - download
      - download.go
    - realRetriever
      - realRetriever.go
    - main.go

// dowload/download.go
package download

type Retriever interface{
	Get(url string) string
}

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

// realRetriever/realRetriever
package realRetriever

import (
	"time"
	"net/http"
	"net/http/httputil"
)

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()

	if err != nil {
		panic(err)
	}
	return string(result)
}

// main.go
package main

import (
	"fmt"
	"time"
	"./download"
	"./realRetriever"
)


func main()  {
	var r download.Retriever // 接口
	r = realRetriever.Retriever{
		UserAgent: "Mozilla/5.0",
		TimeOut: time.Minute,
	} // 调用结构体赋值给接口
	fmt.Printf("%T %v\n", r, r) // 打印出 realRetriever.Retriever {Mozilla/5.0 1m0s}
	// fmt.Println(download.Download(r, "http://49.235.84.2/"))
}
  • 如果结构体Retriever中的属性很多,如果用值的话是拷贝的,性能不好,所以最好是使用指针,那么可以做以下改变:
    • realRetriever/realRetriever.go中的Get方法,接收者改成(r *Retriever)
    • main.go中,将realRetriever.Retriever改成&realRetriever.Retriever
    • 下面的代码,最红打印出的是*realRetriever.Retriever &{Mozilla/5.0 1m0s},可以看出,有两个内容:
      • 一个是类型*realRetriever.Retriever
      • 一个是值&{Mozilla/5.0 1m0s}
// 目录结构
- real
    - download
      - download.go
    - realRetriever
      - realRetriever.go
    - main.go

// dowload/download.go
package download

type Retriever interface{
	Get(url string) string
}

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

// realRetriever/realRetriever
package realRetriever

import (
	"time"
	"net/http"
	"net/http/httputil"
)

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()

	if err != nil {
		panic(err)
	}
	return string(result)
}

// main.go
package main

import (
	"fmt"
	"time"
	"./download"
	"./realRetriever"
)


func main()  {
	var r download.Retriever // 接口
	r = &realRetriever.Retriever{
		UserAgent: "Mozilla/5.0",
		TimeOut: time.Minute,
	} // 调用结构体赋值给接口
	fmt.Printf("%T %v\n", r, r) // 打印出 *realRetriever.Retriever &{Mozilla/5.0 1m0s}
	// fmt.Println(download.Download(r, "http://49.235.84.2/"))
}
- ==重要: == 这里要注意一点,main.go中,main函数第一句创建了一个接口,而第二句表示实现了这个接口,所以就需要去实现接口中的所有方法,也就是这里的Get方法,因为Get方法接收者是指针,所以我们在使用的时候,也是需要将指针赋值给r

判断类型

  • 方法一: r.(type) 就能知道r的类型了
func main()  {
	var r download.Retriever // 接口
	r = &realRetriever.Retriever{
		UserAgent: "Mozilla/5.0",
		TimeOut: time.Minute,
	} // 调用结构体赋值给接口
	fmt.Printf("%T %v\n", r, r)
	inspect(r) // 会打印出UserAgent:  Mozilla/5.0
	
}

func inspect(r download.Retriever)  {
	switch v := r.(type) {
	case *realRetriever.Retriever:
		fmt.Println("UserAgent: ", v.UserAgent)
		// case mock.Retriever:
		// 	fmt.Println("Contents: ", v.Contents)
	}
}
  • 方法二: 利用Type assertion,具体为:
// 判断r是否是*realRetriever.Retriever类型,如果是r.(int)那就是判断r是否为int
realRetriever, ok := r.(*realRetriever.Retriever)
if ok {
  fmt.Println(realRetriever.TimeOut) // 1m0s
}

接口变量里有什么

-------------------------
| 接口变量               |
| ---------   --------- |  
| | 实现者 |  | 实现者 | |
| | 的类型 |  | 的值   | |
| ---------   --------- |
|                       |
-------------------------


在这里,实现者的值,也可能是是实现者的指针,该指针指向实现者:
-------------------------
| 接口变量               |
| ---------   --------- |          ---------- 
| | 实现者 |  | 实现者 | |         |          |
| | 的类型 |  | 的指针 |---------->|  实现者   |
| ---------   --------- |         |          |
|                       |         ------------
-------------------------
  • 接口变量自带指针,当然有可能是值
  • 接口变量同样采用值传递,因为如果接口变量里面自带指针,所以几乎不需要使用接口的指针
  • 指针接收者实现只能以指针方式使用,值接收者都可以

查看接口变量(两种方法)

  • Type Assertion
  • Type Switch

表示任何类型

  • interface{}表示任何类型
  • 举例:
// 目录结构如下
- queue
    - queue
        - queue.go
    - main.go

// main.go
package main

import (
	"fmt"
	"./queue"
)

func main()  {
	q := queue.Queue{}
	q.Push(1)
	q.Push("abc") // 一个切片类型中,数据可以是多种类型了
	fmt.Println(q) // [1 abc]
	fmt.Println(q.Pop()) // [abc]
	fmt.Println(q) // [1]
}

// queue/queue.go
package queue

type Queue []interface{}

func (q *Queue) Push(v interface{}){
	*q = append(*q, v)
}

func (q *Queue) Pop() interface{} {
	lastIndex := (len(*q) - 1)
	lastEle := (*q)[lastIndex : lastIndex + 1]
	*q = (*q)[:lastIndex]
	return lastEle
}

  • 重点: 那么我如果定义一个Queue是任何类型的,但是我在Push的时候要限定为int,这怎么来做?

    • 只需要在Push的参数中限定v的类型不是interface{},而是int
    // queue/queue.go
    package queue
    
    type Queue []interface{}
    
    func (q *Queue) Push(v int){
      *q = append(*q, v)
    }
    
    • 这时候如果执行Push传入字符串abc,就会报错,cannot use “abc” (type untyped string) as type int in argument to q.Push 【编译时错误】
  • 重点: 如果在Pop的时候必须返回的是int,怎么做?

    • 首先返回值改为int
    • 然后return后面因为不确定是否为int,所以需要强制转换为int,用.(int)
    // queue/queue.go
    func (q *Queue) Pop() int {
      lastIndex := (len(*q) - 1)
      lastEle := (*q)[lastIndex : lastIndex + 1]
      *q = (*q)[:lastIndex]
      return lastEle.(int)
    }
    
    • 这时候如果执行Push,那么当遇到字符串abc的时候,就会报错,invalid type assertion: lastEle.(int) (non-interface type Queue on left) 【编译时错误】
  • 重点: 如果传入的参数都是任何类型,但是在内部值append一个int,那么可以这么做:

    • 把append函数中需要加入的元素转成int,即v.(int)
      func (q *Queue) Push(v interface{}){
        *q = append(*q, v)
      }
    
    • 这时候不会编译错误,但是会【运行时错误】

接口组合

  • 对于多个接口,可以组合成一个新接口使用,例如:
type Retriever interface{
  Get(url string) srting
}
type Poster interface{
  Post(url string, form map[string]string) string
}

// 对于以上两个接口,可以组合成一个接口,比如
type RetrieverPoster interface{
  Retriever
  Poster
  // 当然还可以有其他的方法或接口一起组合,比如下面的方法Connect
  Connect(host string)
}

  • 如何调用组合接口里的方法,比如调用RetrieverPoster下的Connect和RetrieverPoster下的Retriever下的Get
    • 调用RetrieverPoster下的Connect
    // 调用RetrieverPoster下的Connect,由于Connect是一个单纯的方法,所以直接调用
    func Session(s RetrieverPoster) string{
      s.Connect("xxxx")
    }
    
    • 调用RetrieverPoster下的Retriever下的Get
    // 调用RetrieverPoster下的Retriever下的Get,可以跨级调用,省略中间的Retriever
    func Session(s RetrieverPoster) string{ // 如果接口组合了之后,那么就可以直接掉组合的元素接口的API,比如直接调用RetrieverPoster下的Retriever下的Get,即s.Get()
        s.Get("http://www.baidu.com")
    }
    

举例

实现一个先post一个字符串"hello YH",然后get到这个字符串并打印出来

package main
import (
	"fmt"
)

// 定义接口
type Poster interface{
	Post(url string, form map[string]string) string
}

type Geter interface{
	Get(url string) string
}

type Deliver interface{
	Poster
	Geter
}

// 定义使用者如何使用
const URL = "http://www.baidu.com"
func deliverFn(d Deliver) string {
	d.Post(URL, map[string]string{"content": "hello YH"})
	return d.Get(URL)
}


// 实现接口
type DeliverComeTrue struct{
	ContentList map[string]string
}

func (d DeliverComeTrue) Post(url string, form map[string]string) string  {
	d.ContentList[url] = form["content"]
	return "ok"
}

func (d DeliverComeTrue) Get(url string) string {
	return d.ContentList[url]
}

func main()  {
	var d Deliver
	d = DeliverComeTrue{ContentList: map[string]string{}}

	fmt.Println(deliverFn(d))
}
  • 在这里,要注意,map需要初始化后才可以赋值

常用的系统接口

  • Stringer接口

Stringer接口定义在fmt包中,该接口包含String()方法。任何类型只要定义了String()方法,进行Print输出时,就可以得到定制输出。

type Stringer interface {
    String() string
}
  • 我们需要去实现String方法用于实现该接口
  package main

  import (
    "fmt"
  )
  type FormatString struct{
    Contents string
  }

  func (f *FormatString) String() string {
    return fmt.Sprintf("FormatString{Contents=%v}", f.Contents)
  }

  func main()  {
    str := &FormatString{Contents: "This is a string"}
    fmt.Println(str)
  }

  • read/write接口
func printFile(filename string) {

    file, e := os.Open(filename)

    if e != nil {
        panic(e)
    }

    printContentFile(file)

}

func printContentFile(reader io.Reader) {//使用reader接口作为参数
    scanner := bufio.NewScanner(reader)
    for scanner.Scan() {
        println(scanner.Text())
    }
}

// 当我们使用reader接口作为参数后,就不止打印文件内的信息,还可以打印字符串:
func main() {
    printFile("adc.txt")
    println("---------")
    s:=`asd

    sss
    www
    111
    1` //使用``框起来的字符串,可以做出换行与引号的字符串
    printContentFile(strings.NewReader(s))
    /*
      asdd
      1111
      cccc
      wqwe
      ---------
      asd

          sss
          www
          111
    1
    
    */
}