接口的概念

为什么要有接口

接口可以理解为: 接口是一系列方法和特征的集合。(方法和特征是类的组成部分)

接口只是一个框架而没有实现,因此在接口定义时不需要考虑接口中的方法如何实现。利用接口可达到实现多继承的目地。可以在不暴露对象的类的前提下,暴露对象的编程接口。不用强迫类关系在无关类中截获相似处(采用适配器就可以了)。声明想执行的一个或多个方法。

那么为何要定义接口?以及接口有何意义呢?

  1. 定义接口有利于代码的规范:对于一个大型项目而言,架构师往往会对一些主要的接口来进行定义,或者清理一些没有必要的接口。这样做的目的一方面是为了给开发人员一个清晰的指示,告诉他们哪些业务需要实现;同时也能防止由于开发人员随意命名而导致的命名不清晰和代码混乱,影响开发效率。
  2. 有利于对代码进行维护:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现现有的类已经不能够满足需要,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
  3. 保证代码的安全和严密:一个好的程序一定符合高内聚低耦合的特征,那么实现低耦合,定义接口是一个很好的方法,能够让系统的功能较好地实现,而不涉及任何具体的实现细节。这样就比较安全、严密一些,这一思想一般在软件开发中较为常见。

Go 中的接口

Go 接口是一组方法的集合,可以理解为抽象的类型。它提供了一种非侵入式的接口。任何类型,只要实现了该接口中方法集,那么就属于这个类型。

举个例子,假设定义一个鸭子的接口。如下:

type Duck interface {
    Quack()   // 鸭子叫
    DuckGo()  // 鸭子走
}

假设现在有一个鸡类型,结构如下:

type Chicken struct {
}

func (c Chicken) IsChicken() bool {
    fmt.Println("我是小鸡")
}

这只鸡和一般的小鸡不一样,它比较聪明,也可以做鸭子能做的事情。

func (c Chicken) Quack() {
    fmt.Println("嘎嘎")
}

func (c Chicken) DuckGo() {
    fmt.Println("大摇大摆的走")
}

注意,这里只是实现了 Duck 接口方法,并没有将鸡类型和鸭子接口显式绑定。这是一种非侵入式的设计。

我们定义一个函数,负责执行鸭子能做的事情。

func DoDuck(d Duck) {
    d.Quack()
    d.DuckGo()
}

因为小鸡实现了鸭子的所有方法,所以小鸡也是鸭。那么在 main 函数中就可以这么写了。

func main() {
    c := Chicken{}
    DoDuck(c)
}

执行正常。如此是不是很类似于其他语言的多态,其实这就是 Go 多态的实现方法。

duck typing(鸭子类型)

鸭子气球

上述图片到底是不是鸭子?

在小孩看来,这就是鸭子,在大人看来,这就是个气球,它不是鸭子

“像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子” 意思就是: 一个东西究竟是不是鸭子,取决于它能不能满足鸭子的工作。

duck typing 多见于动态语言,例如PHP,Python等.

在静态语言中比较罕见,但是go语言属于结构化类型语言,类似于duck typing,在go中duck typing处处都是,duck typing.go的interface{}和duck typing密不可分.

原理说明

duck typing描述事物的外部行为而非内部结构

在面向对象的编程语言中,当某个地方(比如某个函数的参数)需要符合某个条件的变量(比如要求这个变量实现了某种方法)时,什么是判断这个变量是否“符合条件”的标准?

如果某种语言在这种情况下的标准是: 这个变量的类型是否实现了这个要求的方法(并不要求显式地声明),那么这种语言的类型系统就可以称为 duck typing

鸭子类型(英语:duck typing):

是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。

简单来说,谁调用谁实现,比如你要调用一个A函数,而A函数中接收参数的同时调用了参数中的B方法.这时候你就要传进去一个具有B方法名的一个对象.

代码示例

python中的duck typing

#!/usr/local/bin/python3
# coding=utf8

# 使用的对象和方法
class PsyDuck(object):
    def gaga(self):
        print("这是可达鸭")

# 使用的对象和方法
class DoningdDuck(object):
    def gaga(self):
        print("这是唐老鸭")

# 被调用的函数
def duckSay(func):
    return func.gaga()

# 限制调用方式
if __name__ != '__main__':
    print("must __main__")

# 实例化对象
duck = PsyDuck()
person = DoningdDuck()

# 调用函数
duckSay(duck)
duckSay(person)

go

package main

import "fmt"

//定义一个鸭子接口
//Go 接口是一组方法的集合,可以理解为抽象的类型。它提供了一种非侵入式的接口。任何类型,只要实现了该接口中方法集,那么就属于这个类型。
type Duck interface {
	Gaga()
}

//假设现在有一个可达鸭类型
type PsyDuck struct{}

//可达鸭声明方法-满足鸭子会嘎嘎叫的特性
func (pd PsyDuck) Gaga() {
	fmt.Println("this is PsyDuck")
}

//假设现在有一个唐老鸭类型
type DonaldDuck struct{}

//唐老鸭声明方法-满足鸭子会嘎嘎叫的特性
func (dd DonaldDuck) Gaga() {
	fmt.Println("this is DoningdDuck")
}

//要调用的函数 - 负责执行鸭子能做的事情,注意这里的参数,有类型限制为Duck接口
func DuckSay(d Duck) {
	d.Gaga()
}

func main() {
	//提示开始打印
	fmt.Println("duck typing")

	//实例化对象
	var pd PsyDuck	//可达鸭类型
	var dd DonaldDuck	//唐老鸭类型

	//调用方法
	//pd.Gaga()	//因为可达鸭实现了所有鸭子的函数,所以可以这么用
	//dd.Gaga()	////因为唐老鸭实现了所有鸭子的函数,所以可以这么用
	DuckSay(pd)       //因为可达鸭实现了所有鸭子的函数,所以可以这么用
	DuckSay(dd)       //因为唐老鸭实现了所有鸭子的函数,所以可以这么用
}

接口的定义和实现

Go中接口是由使用者来定义的

接口的定义

接口是和调用方的一种约定,它是一个高度抽象的类型,不用和具体的实现细节绑定在一起。接口要做的是定义好约定,告诉调用方自己可以做什么,但不用知道它的内部实现,这和我们见到的具体的类型如 int、map、slice 等不一样。

接口的定义和结构体稍微有些差别,虽然都以 type 关键字开始,但接口的关键字是 interface,表示自定义的类型是一个接口。

也就是说 person 是一个接口,它有两个方法 sayName() string 和 sayAge() int,整体如下面的代码所示:

type person interface {
	sayName() string
	sayAge() int
}

针对 person 接口来说,它会告诉调用者可以通过它的 sayName() 方法获取姓名字符串,通过它的 sayAge() 方法获取年龄,这就是接口的约定。至于这个字符串怎么获得的,长什么样,接口不关心,调用者也不用关心,因为这些是由接口实现者来做的。

接口特点:

接口只有方法声明、没有实现,没有数据字段
接口可以匿名嵌入其它接口,或者嵌入到结构中

接口是用来定义行为的类型,这些被定义的行为不由接口直接实现,而是由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。

接口的实现

接口的实现者必须是一个具体的类型,以 student 结构体为例,

type student struct {
	name string
	age  int
}

让它来实现 person 接口,如下代码所示:

func (s student) sayName() string {
	fmt.Printf("name is %v\n", s.name)
	return s.name
}

func (s student) sayAge() int {
	fmt.Printf("name is %v\n", s.age)
	return s.age
}

给结构体类型 student 定义一个方法,这个方法和接口里方法的签名(名称、参数和返回值)一样,这样结构体 student 就实现了 person接口。

注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。

另外一个例子:

接口的声明与定义:

type Shape interface {
  perimeter() float64
  area() float64
}
var s Shape

隐式地让一个类型实现接口:

type Rectangle struct {
  a, b float64
}
func (r Rectangle) perimeter() float64 {
  return (r.a + r.b) * 2
}
func (r Rectangle) area() float64 {
  return r.a * r.b
}

接口的动态调用方式:

var s Shape
s = Rectangle{3, 4}
s.perimeter()
s.area()

接口嵌套

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

接口断言

func main(){
  var s Shape
  s = Rectangle{3, 4}
  rect := s.(Rectangle)
  fmt.Printf("长方形周长:%v, 面积:%v \\n",rect.perimeter(),rect.area())
}

空接口

如果一个 interface 中如果没有定义任何方法,即为空 interface,表示为 interface{}。如此一来,任何类型就都能满足它,这也是为什么当函数参数类型为 interface{} 时,可以给它传任意类型的参数。

示例代码,如下:

package main

import "fmt"

func main() {
    var i interface{} = 1
    fmt.Println(i)
}

接口的值类型

接口变量内容

main

package main

import (
	"fmt"
	"learngo/retriever/mock"
	"learngo/retriever/test"
	"time"
)

type Retriever interface {
	Get(url string) string
}

func download(r Retriever) string {
	return r.Get("https://blog.csdn.net/qq_43135259/article/details/123298792?spm=1001.2014.3001.5502")
}

func main() {
	r := mock.Retriever{"番茄炒蛋", time.Minute}
	// HTTP/2.0 200 OK
	//fmt.Println(download(r))

	// mock.Retriever {番茄炒蛋 1m0s}
	fmt.Printf("%T %v\n", r, r)
	rr := test.Retriever{"this is fake contents"}
	// this is fake contents
	//fmt.Println(download(rr))

	// test.Retriever {this is fake contents}
	fmt.Printf("%T %v\n", rr, rr)
}

r和rr里面有一个类型,和自己真实的value

mock.Retriever

package mock

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

type Retriever struct {
	UserAgrent string
	TimeOut       time.Duration
}

func (r Retriever) Get(url string) string {
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	bytes, err := httputil.DumpResponse(resp, true)
	if err != nil {
		panic(err)
	}
	return string(bytes)
}

test.Retriever

package test

type Retriever struct {
	Contents string
}

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

通过指针访问

mock.Retriever

package mock

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

type Retriever struct {
	UserAgrent string
	TimeOut       time.Duration
}

func (r *Retriever) Get(url string) string {
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	bytes, err := httputil.DumpResponse(resp, true)
	if err != nil {
		panic(err)
	}
	return string(bytes)
}

main

package main

import (
	"fmt"
	"learngo/retriever/mock"
	"learngo/retriever/test"
	"time"
)

type Retriever interface {
	Get(url string) string
}

func download(r Retriever) string {
	return r.Get("https://blog.csdn.net/qq_43135259/article/details/123298792?spm=1001.2014.3001.5502")
}

func main() {
	r := &mock.Retriever{"番茄炒蛋", time.Minute}
	// HTTP/2.0 200 OK
	//fmt.Println(download(r))

	// *mock.Retriever &{番茄炒蛋 1m0s}
	fmt.Printf("%T %v\n", r, r)
	rr := test.Retriever{"this is fake contents"}
	// this is fake contents
	//fmt.Println(download(rr))

	// test.Retriever {this is fake contents}
	fmt.Printf("%T %v\n", rr, rr)
}

获取接口的类型

case方式访问

package main

import (
	"fmt"
	"learngo/retriever/mock"
	"learngo/retriever/test"
	"time"
)

type Retriever interface {
	Get(url string) string
}

func download(r Retriever) string {
	return r.Get("https://blog.csdn.net/qq_43135259/article/details/123298792?spm=1001.2014.3001.5502")
}

func main() {
	var r Retriever = &mock.Retriever{"番茄炒蛋", time.Minute}
	// HTTP/2.0 200 OK
	//fmt.Println(download(r))

	// UserAgent: 番茄炒蛋
	funcName(r)

	if mockRetriever, ok := r.(*mock.Retriever); ok {
		// TimeOut: 1m0s
		fmt.Println("TimeOut:", mockRetriever.TimeOut)
	}else {
		fmt.Println("not a mock Retriever")
	}



	r = test.Retriever{"this is fake contents"}
	// this is fake contents
	//fmt.Println(download(rr))

	// ontents: this is fake contents
	funcName(r)

}

func funcName(r Retriever) {
	switch v := r.(type) {
	case *mock.Retriever:
		fmt.Println("UserAgent:", v.UserAgrent)
	case test.Retriever:
		fmt.Println("contents:", v.Contents)
	}
}

  • 接口变量自带指针

  • 接口变量同样采用值传递,因为接口变量里面有一个指针,所以几乎不需要使用接口的指针

  • 指针接收者实现只能以指针的方式来使用;值接收者两者都可以

查看接口变量:

  • Type Assertion
  • Type Switch
  • 表示任何类型:interface{} (go语言任何类型表示法)

接口的组合

// 接口类型可以进行嵌套
//定义总接口
type animal interface {
	peo
	dog
	cat
}
//定义包含的接口
type peo interface {
}
type dog interface {
}
type cat interface {
}