go语言的接口

go语言的接口

简介

  • 接口是双方规定的一种合作协议,接口实现者不需要关心接口会被怎样使用,调用者不需要关心接口的实现细节。接口是一种类型。也是一种抽象结构。不会暴露所含数据的格式、类型及结构。比如只要一台洗衣机有洗衣服的功能和甩干的功能,我们就称为洗衣机,不关心属性(数据),只关心行为(方法)。
  • 接口和其他动态语言的鸭子模型有密切关系。比如说pythonjavascript。任何类型,只要实现了该接口中的方法集,那么就属于这个类型。
  • 每个接口由数个方法组成。

接口的定义

格式:

type 接口类型名 interface {
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}
  • 接口类型名:使用type将接口定义为自定义的类型名
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表的参数变量名可以被忽略

示例:

// 可以吃
type Eat interface {
    eat()
}

看到这个接口时,只知道他有一个eat()方法。并不知道谁实现了这些接口,也不知道是怎么实现的这些这个方法的。

实现接口的条件

  • 接口的方法与实现接口的类型方法格式一致
  • 接口的方法与实现接口的类型方法数目保持一致。即接口中的所有方法均被实现。
package main

import (
	"fmt"
)

// 定义一个鸭子类型接口
type Duck interface {
	// 鸭子叫
	GaGa()
	// 鸭子走
	YouYong()
}

// 定义一个小鸡类型的结构体
type Chicken struct {
}

// 为小鸡结构体实现GaGa
func (c Chicken) GaGa() {
	fmt.Println("我是小鸡,但我也会嘎嘎叫")
}

// 为小鸡结构体实现YouYong
func (c Chicken) YouYong() {
	fmt.Println("我是小鸡,但我也会游泳")
}

func main() {
    
	// 实例化一个小鸡
	c := new(Chicken)

	// 初始化一个小鸡小鸭合体类型
	var duckChicken Duck = c

	// 小鸡小鸭开始嘎嘎
	duckChicken.GaGa()
	// 小鸡小鸭开始游泳
	duckChicken.YouYong()

}

可以看出来,duckChicken可以直接调用GaGaYouYong。他并不知道这两个方法内部怎么实现的。

值类型接收者和指针型接收者实现接口

package main

import (
	"fmt"
)

// 定义一个汪汪叫的接口
type WangWager interface {
	Wang()
}

// 定义一个狗结构体
type Dog struct {
	name string
}

func (d Dog) Wang()  {
	fmt.Println("汪汪叫")
}

func main() {
	d1 := Dog{"小狗1"}
	d2 := &Dog{
		"小狗2",
	}
	var w1 WangWager = d1
	w1.Wang()

	var w2 WangWager = d2
	w2.Wang()

}

可以发现,使用值类型实现接口后,不管dog结构体实例化是指针型还是值类型。都可以赋值给该接口变量。因为go语言内部有对指针类型变量求值的语法糖。

但是指针型实现接口后,只能指针型变量赋值给接口变量。

类型与接口的关系

类型与接口有一对多和多对多的关系。

一(类型)对多(接口):

package main

import (
	"fmt"
)

// 定义一个Writer接口
type Writer interface {
	Writer(p []byte) (n int, err error)
}

// 定义一个Closer接口
type Closer interface {
	Closer() error
}

// 定义一个socket结构体类型
type Socket struct {
}

// 为socket实现一个Writer()方法
func (s *Socket) Writer(p []byte) (n int, err error) {
	fmt.Println("开始写入")
	return n, err
}

// 为socket实现一个Closer()方法
func (s *Socket) Closer() (err error) {
	fmt.Println("开始关闭")
	return err
}

// 定义一个函数,负责写
func useWriter(w Writer) {
	// 执行w接口的写方法
    _, _ := w.Writer(nil)
}
// 定义一个函数,负责关闭
func useCloser(c Closer) {
	// 执行c接口的关闭方法
    _ := c.Closer()
}

func main() {
	fmt.Println("理解类型与接口的关系")

	// 类型和接口之间有一对多和多对一的关系。

	// 一个类型可以实现多个接口
	// 实例化socker结构体
	s := new(Socket)
	useWriter(s)
	useCloser(s)
}

可以看出两个函数完全独立,完全不知道对方的存在,也不知道使用自己的接口是socket使用的

多(类型)对一(接口)

接口的方法不一定要一个结构体类型完全实现,接口的方法可以通过结构体嵌入实现。

package main

import (
	"fmt"
)

// 定义一个服务接口,实现了服务开启和日志输出的方法
type Service interface {
	Start()     // 启动服务
	Log(string) // 日志输出
}

// 定义一个游戏服务结构体
type GameService struct {
	Logger // 内嵌logger结构体
}

// 为游戏结构体实现游戏服务的启动method
func (g *GameService) Start() {
	fmt.Println("游戏服务启动成功")
}

// 定义一个日志器结构体
type Logger struct {
}

// 为日志服务器结构体实现日志输出的method
func (l *Logger) Log(s string) {
	fmt.Println(s)
}

func main() {

	// 多个类型可以实现相同的接口
	var ser Service = new(GameService)
	ser.Start()
	ser.Log("日志输出")
}

可以看出,服务接口下一个服务启动功能和日志输出功能,但是游戏服务类型并没有实现日志输出功能,而是间接通过内嵌日志类型来实现,日志类型实现了日志输出功能,所有游戏服务类型实现的接口可以直接使用日志输出功能。、

接口的嵌套组合

不仅类型与类型之间可以嵌套,接口与接口之间也可以嵌套。

package main

import (
	"fmt"
)

// 定义一个Say接口
type Say interface {
	Say(s string)
}

// 定义一个Run接口
type Run interface {
	Run(n int)
}

// 定义一个Skill接口
type Skill interface {
	// 嵌套了两个接口
	Say
	Run
}

// 定义一个Person结构体
type Person struct {
}

// 为Person结构体实现Say方法
func (p *Person) Say(s string) {
	fmt.Println("说话:", s)
}

// 为Person结构体实现Run方法
func (p *Person) Run(n int) {
	fmt.Println("步数:", n)
}

func main() {
	// 实现人的结构体的技能类型
	var s Skill = new(Person)
	s.Say("Life Is Short Let's Go")
	s.Run(9999)

	// 实现哑巴(人)结构体的技能类型,无法说话,只能跑步
	var yaBa Run = new(Person)
	yaBa.Run(100000)

}

空接口

空接口是接口类型的特殊形式,空接口没有任何方法。因此任何类型都无须实现,从实现的角度来看。任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从中取值。

保存值

package main

import (
	"fmt"
)

func main() {
	// 将各种数据类型的值保存到空接口
	var any interface{}

	any = 1
	fmt.Println(any)
	any = 0.99
	fmt.Println(any)
	any = "Hello Gp"
	fmt.Println(any)
	any = true
	fmt.Println(any)
	any = []string{}
	fmt.Println(any)
	any = [...]string{}
	fmt.Println(any)
	any = map[string]int{}
	fmt.Println(any)
}

空接口的应用

空接口作为函数的参数
package main

import "fmt"

func show(v interface{})  {
	fmt.Println(v)
}

func main() {
	show("江子牙")
	show(520)
	show(true)
}
空接口作为map的value
package main

import "fmt"

func main() {
	a := map[string]interface{}{
		"name":    "江子牙",
		"age":     21,
		"isLogin": true,
	}
	fmt.Println(a)
}

接口和类型之间的转换

go 语言中使用接口断言(type assertions) 将接口转换成另一外一个接口,也可以将接口转另外的类型。使用非常频繁。

类型断言

格式:

t := i.(T)
  • i:代表接口变量
  • T:代表转换的目标类型
  • t:转换后的变量

如果i没有完全实现T接口的方法,这个语句会触发宕机,触发宕机不是很友好,因为有另一种保守写法。

t, ok := i.(T)

这种写法的好处就是如果发送接口未实现时,将会返回一个布尔值false,即ok的值,而且t的类型为0。正常实现时,oktrue

接口转化为其他接口

例子:

package main

import (
	"fmt"
)

// 定义飞行动物接口
type Flyer interface {
	Fly(s string)
}

// 定义爬行动物接口
type Walker interface {
	Walk(s string)
}

// 定义小鸟类结构体
type Bird struct {
}

// 定义小猪类结构体
type Pig struct {
}

// 为小鸟类实现飞的技能和走路的技能
func (b *Bird) Fly(s string) {
	fmt.Println("小鸟飞行:", s)
}
func (b *Bird) Walk(s string) {
	fmt.Println("小鸟走路:", s)
}

// 为小猪实现走路的技能
func (p *Pig) Walk(s string) {
	fmt.Println("死猪跑不动吗:", s)
}

func main() {
	// 先创建一个字典来存放接口的信息。
	animals := map[string]interface{}{
		"bird": new(Bird),
		"pig":  new(Pig),
	}
	fmt.Println(animals)
	// 循环map
	for name, obj := range animals {
		// 类型断言:判断obj是爬行动物还是飞行动物
		f, isFly := obj.(Flyer)
		w, isWalk := obj.(Walker)
		fmt.Printf("name:%s\tisFlyer:%v\tisWalker:%v\n", name, isFly, isWalk)

		// 类型判断
		if isFly {
			f.Fly("100米")
		}
		if isWalk {
			w.Walk("1000步")
		}
	}
}

接口转化为类型

package main

import (
	"fmt"
)

// 定义爬行动物接口
type Walker interface {
	Walk(s string)
}

// 定义小猪类结构体
type Pig struct {
}

// 为小猪实现走路的技能
func (p *Pig) Walk(s string) {
	fmt.Println("死猪跑不动吗:", s)
}

func main() {
	// 将接口转为其他类型:可以实现将接口转为普通的指针类型
	// 实例化出一个小猪结构体
	p1 := new(Pig)
	// 声明一个类型为小猪爬行类w接口
	var w Walker = p1
	fmt.Println(w)
	fmt.Printf("%T\n", w)
	fmt.Printf("%T\n", p1)
	// 将w接口转为*Pig类型
	p2 := w.(*Pig)
	fmt.Printf("p1 = %p\np2 = %p", p1, p2)
}

执行结果:

&{}
*main.Pig
*main.Pig
p1 = 0x5861b0
p2 = 0x5861b0

判断接口中变量的类型

判断基本类型

package main

import (
	"fmt"
)

func printType(i interface{}) {
	switch i.(type) {
	case int:
		fmt.Println("int类型")
	case string:
		fmt.Println("string类型")
	case bool:
		fmt.Println("bool类型")
	default:
		fmt.Println("不知名类型")
	}
}

func main() {
	// 使用类型分支判断基本类型
	printType("str")
	printType(1)
	printType(true)
	printType([]string{})
}

执行结果:

string类型
int类型
bool类型
不知名类型

判断接口类型

package main

import (
	"fmt"
)

// 刷脸的接口
type useFace interface {
	Face(string)
}

// 刷人民币值为100的接口
type useOneHundred interface {
	OneHundred(string)
}

// 支付宝方式结构体
type Alipy struct {
}

// 现金方式方式结构体
type Cash struct {
}

// 为支付宝提供人脸支付的方法
func (a Alipy) Face(s string) {
	fmt.Println(s)
}

// 为现金支付提供支付100元的方法
func (c Cash) OneHundred(s string) {
	fmt.Println(s)
}

func printPay(payMethod interface{}) {
	var pay = payMethod
	// 判断接口类型
	switch payMethod.(type) {
	case useFace:
		face := pay.(*Alipy)
		face.Face("刷脸")
	case useOneHundred:
		cash := pay.(*Cash)
		cash.OneHundred("支付100毛爷爷")
	}
}

func main() {

	//使用类型分支判断接口类型
	printPay(new(Alipy))
	printPay(new(Cash))
}

执行结果:

刷脸
支付100毛爷爷
posted @ 2019-07-16 18:03  爬呀爬Xjm  阅读(699)  评论(0编辑  收藏  举报