前言:

接口是一种以type关键字声明的动态的值和动态数据类型,是一种抽象出来的数据类型,接口定义了一堆方法(规范),只要结构体实现了接口中定义的方法(规范),该结构体就属于这种接口类型。

例如某大神想要开发1款新的操作系统,这个操作系统需要支持各种厂商各种型号的网卡驱动、硬盘驱动、显卡驱动.....

怎么兼容各大厂商、各种型号的驱动插件呢? 

Linux一切皆文件,各种驱动插件实现时都会遵循了1个接口规范,从而实现了九九归一的设计思想。

我们把这些不同硬件都抽象成1种接口类型,而内核程序在调用这些不同的驱动时,按照接口规范的固定的方法调用它们即可。核心程序无需关注各个子模块的细节。

接口可以让我们的程序变得可扩展性极强

package main

import "fmt"

//usb interface规定都要实现 usb接口中的start和end方法都可以称为usb interface类型
type usb interface {
	statr()
	end()
}

//相机 struct
type camera struct{ brand string }

//相机实现usb interface的start方法
func (c camera) statr() {
	fmt.Printf("%s相机插入电脑的USB接口\n", c.brand)
}

//相机实现usb interface的end方法
func (c camera) end() {
	fmt.Printf("%s相机插入电脑的USB接口 \n", c.brand)
}

//手机 struct
type phone struct{ brand string }

//手机实现usb interface的start方法
func (p phone) statr() {
	fmt.Printf("%s手机插入电脑的USB接口\n", p.brand)
}

//手机实现usb interface的end方法
func (p phone) end() {
	fmt.Printf("%s手机拔出电脑的USB接口\n", p.brand)
}

//电脑 struct
type computer struct{ brand string }

//电脑的usbWork这里体现多态的重点!仅接受usb类型的参数(实现start和end方法就属于usb 接口类型) who cares what kind of ekectronics it is?
func (c computer) usbWork(e usb) {
	e.statr()
	e.end()

}

func main() {
	lenov := computer{brand: "lenov"}
	Jphone := phone{brand: "Jphone"}
	Soni := camera{brand: "soni"}
	lenov.usbWork(Jphone)
	lenov.usbWork(Soni)

}

  

Golang为什么需要 interface 类型

之前使用Python、JavaScript..动态类型的语言较多,对面向对象中多态特的特性感受不是很深刻。

由于Golang是强类型的语言,变量一经声明不存在数据类型自动识别/修改。所以它需要接口规范

现在我有1个struct human和另1个struct cats它们都有自己共有的属性-feet、方法-move。

如何使这2个struct都可以作为同1种类型的函数参数,传入到1个run函数中,并执行它们共有的方法move(),输出2种动物2种不同的走路方式

//1.Go中引出接口概念

package main

import "fmt"

/*
定义1个animal接口:只要有类型接口中的规范有move方法,
就可以称之为这种animal interface类型
*/
type animal interface {
	move()
}

//hunma 结构体
type human struct {
	name  string
	feet uint8
}

//人类实现了 move方法,就属于animal interface类型
func (h human) move() {
	fmt.Printf("%s 使用 %d条腿走路~\n", h.name, h.feets)
}

//dogs 猫科动物结构体
type cats struct {
	name  string
	feet uint8
}

//猫科动物实现了move方法,也属于animal interface类型
func (d cats) move() {
	fmt.Printf("%s 使用 %d条腿走路~\n", d.name, d.feets)
}

/*
run函数接收值为aniaml接口类型的参数
现在human和animal的类型一致(都实现了move方法!)
所有都可以作为参数传到run函数里面
*/
func run(a animal) {
	a.move()

}

func main() {
	p1 := human{name: "光头强", feet: 2}

	var d1 cats
	d1.name = "熊二"
	d1.feet = 4

	run(p1)
	run(d1)

}

  

对不同类型进行接口约束

package main

import (
	"fmt"
)

//自行车
type bicycle struct {
	brand string
}

//自行车实现得能开
func (b bicycle) drive() {
	fmt.Printf("I've got a/an %s\n.", b.brand)
}

//四轮汽车也得能开
type car struct {
	brand string
}

func (c car) drive() {
	fmt.Printf("I've got a/an %s.\n", c.brand)
}

//飞机也得能开
type airplane struct {
	brand string
}

func (a airplane) drive() {
	fmt.Printf("I've got a/an %s.\n", a.brand)
}

//定义1个transport 接口类型
type transport interface {
	drive()
}

//想要批量生产transport,就得有自己的标准
func factory(t transport) {
	t.drive()
}

func main() {
	var feige = bicycle{
		brand: "飞鸽",
	}
	var bens = car{
		brand: "大奔",
	}
	var boyin747 = airplane{
		brand: "波也",
	}

	factory(feige)
	factory(bens)
	factory(boyin747)

}

  

 

接口的定义与实现

定义

interface类型可以定义1组方法,但是不需要在接口内部实现这些方法。并且interface内部不能包含任何变量

哪个自定义类型 (如struct)想要使用哪个接口时,需要把哪个接口中定义的方法全部实现。

type 接口名称 interface {
	方法名1(参数1,参数2)(返回值1,返回值2)
        方法名2(参数1,参数2)(返回值1,返回值2)
    
}

  

实现

Golang中的接口实现起来灵活、低耦合,因为不需要像Java中1个类 class A implement interface b 通过implement关键字来显式实现。

只要 变量A中含有 接口B中定义的所有方法 ,变量A就可以称为interfaceB类型。

 

接口的使用

接口的使用就是你遵循了该接口中规定的全部标准(实现了接口中定义的方法)之后,你就可以作为该接口类型的变量、参数在Go中畅行无阻的使用。

 

使用值接收者实现接口和使用值接收者实现接口的区别?
使用值接收者实现接口的方法:接口既可以存结构体类型 也能存结构体类型指针的变量
使用指针接收者实现接口的方法:接口只能存储 指针类型的结构体的变量
package main

import "fmt"

//usb interface规定
type usb interface {
	statr()
	end()
}

//相机 struct
type camera struct{ brand string }

//相机使用指针接收者 实现usb interface的start方法
func (c *camera) statr() {
	fmt.Printf("%s相机插入电脑的USB接口\n", c.brand)
}

//相机使用指针接收者 实现usb interface的end方法
func (c *camera) end() {
	fmt.Printf("%s相机插入电脑的USB接口 \n", c.brand)
}

//手机 struct
type phone struct{ brand string }

//手机实现usb
func (p phone) statr() {
	fmt.Printf("%s手机插入电脑的USB接口\n", p.brand)
}

//手机实现usb interface的end方法
func (p phone) end() {
	fmt.Printf("%s手机拔出电脑的USB接口\n", p.brand)
}

//电脑 struct
type computer struct{ brand string }


func (c computer) usbWork(e usb) {
	e.statr()
	e.end()

}

func main() {
	/*
	使用值接收者实现接口和使用值接收者实现接口的区别?:
	使用值接收者实现接口的方法:既可以存结构体类型 也能存结构体类型指针的变量
	使用指针接收者实现接口的方法:接口只能存储 指针类型的结构体的变量
	*/
	var u usb
	//使用值接收者实现接口的方法:既可以存结构体类型的变量、也可以存结构体指针类型的变量
	phone1:=phone{brand:"8848手机"}
	phone2:=phone{brand:"乐视手机"}
	u=phone1
	fmt.Printf("%T\n",u)
	u=&phone2
	fmt.Printf("%T\n",u)

	//使用指针接收者实现接口的方法:接口只能存储 指针类型的变量 
	camera1:=&camera{brand:"苏尼"}
	u=camera1
	fmt.Printf("%T\n",u)

}

  

空接口(来了就是深圳人~)

空接口interface{ } 就是我没有定义任何方法(规范),任意类型不需要实现任何方法(规范) 就可以作为空接口类型作为变量和参数使用

为什么 fmt.Print( )函数可以接收任何数据类型呢?

func Println(a ...interface{}) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

//interface{}就是空接口

空接口是指没有定义任何方法的接口。因此任何变量和参数都属于空接口类型。

空接口类型的变量可以存储任意类型的变量。包揽Golang中任何数据类型。

package main

import "fmt"

type everything interface{}

func main(){
	var m1  map[string]everything
	m1=make(map[string]everything,18)
	m1["name"]="阿水"
	m1["married"]=true
	m1["age"]=19
	m1["hobby"]=[]string{"抽烟","喝酒","烫头"}
	fmt.Println(m1)
	//哈哈.....终于实现Python里面的字典了吧!
	
}

空接口用处

判断传入的空接口类型参数 ,到底类型是什么? 必须结合switch...case...一起使用

package main

import "fmt"

func inArry(needle interface{}, stack interface{}) bool {
    //判断needle参数的具体类型是什么?必须结合switch...case...一起使用
    switch key := needle.(type) {
    //判断类型
    case string:
        //空接口类型强制转换成1种类型
        for _, item := range stack.([]string) {
            if key == item {
                return true
            }

        }
    case int:
        //空接口类型强制转换成1种类型
        for _, item := range stack.([]int) {
            if key == item {
                return true
            }

        }
    default:
        return false
    }
    return false
}

func main() {
    fmt.Println(inArry("ss", []string{"ss1"}))
}

 

  

类型断言和接口的关系

我们使用接口是为了方便统一管理和灵活扩展插件,可是没有把用户的需求一一反射、分发到各个功能插件这些的丰富的插件就犹如一堆滞销产品。

两者的关系有点像开发和销售的关系~唇齿相依~。

当structA、strucB、structC、structD都实现了 interface1接口,它们都可以被称为interface1接口类型。

假设各种插件都实现好了,也遵循了interface1规范。那怎么调用这些丰富的插件呢? 完成集成呢?

首先我们的程序得指定我当前需要调用哪个插件?

我们一般通过用户配置文件、HTTP请求参数告诉main程序,我当前需要调用的插件的名称、插件的方法、插件的属性,这些用户参数每次都可能不一样。

可是用户配置都是字符串,开发了这么多丰富的插件、程序架构也支持扩展了,可是怎么把这些插件的类struct名、函数、属性..和用户输入联动起来?

反射和接口的关系:我们通过interface聚合、兼容了各种不同的插件,最终通过用户输入在main程序通过反射技术把不同的需求调度到各个功能插件本身

StructA 实现了Interface1这个接口之后1个函数把StructA按照Interface1这个接口类型 Return到了main函数中将它赋值给变量为A,我们怎么调用接口中定义的方法呢?

直接A.method() 是不行的,因为接口变量是动态的值和动态的类型,得需要做类型断言/反射确定当前变量A属于哪种具体的类型之后才能调用接口中定义的方法

 接口是一种引用类型,接口变量由2部分组成:动态类型、动态值。

其中type就是它的类型(动态类型),value部分是它的值(动态值)。当2个变量的type+value都相等是才会相等。

package main

import (
	"fmt"
	"reflect"
)

var x interface{}
//接口值
func main(){
	var a int8=1
	var b int32= 10
	var c int64=100
	x=a
	fmt.Printf("x变量的动态类型:%v  动态值:%v\n",reflect.TypeOf(x),x)
	x=b
	fmt.Printf("x变量的动态类型:%v  动态值:%v\n",reflect.TypeOf(x),x)
	x=c
	fmt.Printf("x变量的动态类型:%v  动态值:%v\n",reflect.TypeOf(x),x)



}

既然接口的值是动态的如果,别人在函数里给我返回了1个接口类型的返回值/给我的函数里传了1个空接口变量、

我想使用这个参数?我应该怎么看看它当前是什么类型?什么值呢?猜吧!猜变量当前属于哪种数据类型就叫类型断言

func login(c *gin.Context) {
    sessionData, ok := c.Get(sessions.ContextID)
    if !ok {
        fmt.Println("获取session data 失败!")
    }
    session := sessionData.(*sessions.SessionCell)
    session.Set("username", "张根")
    session.Set("gender", "")
    session.Set("age", "18")
    c.JSON(200, gin.H{"data": "登录成功"})
}

 

类型断言和反射的关系?

除了golang自带的数据类型之外,我们还可以使用type struct关键字在golang中衍生出各种各样的数据类型。

那我使用switch或者if判断得兼容多少case呢?既然数据类型太多了类型断言猜不全,我们就使用reflect 反射包

reflect 反射包可以直接获取到接口变量当前的数据类型、当前的值,不需要再进行判断

reflect.TypeOf ()接口变量当前的数据类型。

reflect.valueof()接口变量当前的值。

所以定义接口(方法)是为了规范多种数据类型为1中类型,既然规范有在调用接口变量的方法时需要借助反射。

 

对象值类型方法和指针类型方法的区别

只有一个类型的方法集完全涵盖了接口的方法集后,这个类型才会被认为是接口的实现类型。

变量类型方法接收器类型适用的对象
T(值类型) (t T) 传T对象能覆盖值类型的方法集、*T对象更能覆盖值类型的方法集
*T(指针类型) (t T) + (t *T) 只能传*T对象才能覆盖指针类型方法的方法集了

对象值类型的方法:    仅包含值类型对象自身的方法集

对象指针类型的方法:指针类型对象本身的方法集+值类型对象的方法集

什么T的方法集是*T方法集的子集?

package main

import (
    "fmt"
)

type Animal interface {
    Say()
    SetName(string2 string)
    GetName()
}

type Dog struct {
    name string
}

func (self *Dog) Say() {
    fmt.Println(self.name, "在叫......")
}
func (self *Dog) SetName(name string) {
    self.name = name
}

func (self *Dog) GetName() {
    fmt.Println(self.name)
}

func main() {
    //问题1:
    //对象值类型的方法,也自动包含了指针类型的方法,因为Go语言在编译期做了优化;
    //而指针类型的方法不含值类型的方法。
    var d Animal
    d = Dog{name: "SS"} //报错
    d = &Dog{name: "SS"}
    fmt.Println(d)
    //问题2:指针类型的方法才能修改对象属性,值类型的方法传对象指针也不行
    var aList []Animal //[]接口原来也可以实例化
    aList = append(aList, &Dog{"A"})
    for _, a := range aList {
        a.SetName("B")
    }
    for _, a := range aList {
        a.GetName()
    }

}

 

 

 

 

参考

 

posted on 2020-04-13 09:19  Martin8866  阅读(565)  评论(0编辑  收藏  举报