《Go语言从入门到进阶实战》学习笔记:第七章 接口

7.1声明接口

接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。

接口是一种类型也是一种抽象结构,不会暴露所含数据的格式、类型及结构。

1、接口声明格式

type 接口类型名interface{
    方法名1(参数列表1)返回值列表1
    方法名2(参数列表2)返回值列表2
...
}

接口类型名:一般在字母最后加er

方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。

参数列表、返回值列表:参数列表、返回值列表中的参数变量名可以被忽略。

2、开发中常见的接口及写法

io包中提供的Writer接口:

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

Stringer接口

type Stringer interface{
    String() string
}

7.2实现接口的条件

1、条件一:接口的方法与实现接口的类型方法格式一致

数据写入器的抽象

package main

import "fmt"

//定义一个数据写入器
type Data_Writer interface {
	Write_Data(data interface{})error
}

//定义文件结构,用于实现Data_Writer
type file struct {

}

//实现Data_Writer接口的Write_Data()方法
func (d *file)Write_Data(data interface{})error  {
	//模拟写入数据
	fmt.Println("Write_Data:",data)
	return nil
}

func main()  {
	//实例化file
	f:=new(file)
	//声明一个Data_Write的接口
	var writer Data_Writer
	//将接口赋值f,也就是*file类型
	writer=f
	//使用Data_Write接口进行数据写入
	writer.Write_Data("data")
}

本例中实现及调用关系图

2、条件二:接口中所有方法均被实现

当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译使用。

7.3理解类型与接口的关系

1、一个类型可以实现多个接口

2、多个类型可以实现相同的接口

接口方法可以通过在类型中嵌入其他类型或者结构体来实现。

service开启服务的方法(Start())和输出日志的方法(Log())实现

package main

//一个服务需要满足能够开启和写日志的功能
type Service interface {
	Start()//开启服务
	Log(string)//日志输出
}
//日志器
type Logger struct {

}
//实现Service的Log()方法
func (g *Logger)Log(l string)  {

}
//游戏服务
type Game_Service struct {
	Logger//嵌入日志
}

//实现Service的Start()方法
func (s *Game_Service)Start()  {

}

func main()  {
	var s Service=new(Game_Service)
	s.Start()
	s.Log("hello")
}

7.4示例:便于扩展输出方式的日志系统

一个支持多种写入器的日志系统

日志写入器

package main

//声明日志写入接口
type Log_Writer interface {
	Write(data interface{})error
}

//日志器
type Logger struct {
	//这个日志器用到的日志写入器
	writer_List []Log_Writer
}
//注册一个日志写入器
func (l *Logger) Register_Writer(writer Log_Writer) {
	l.writer_List=append(l.writer_List,writer)
}
//将一个data类型的数据写入日志
func (l *Logger)Log(data interface{})  {
	//遍历所有注册的写入器
	for _,writer:=range l.writer_List{
		//将日志输出到每个写入器中
		writer.Write(data)
	}
}

//创建日志器的实例
func New_Logger()*Logger  {
	return &Logger{}
}

(2)文件写入器

package main

import (
	"errors"
	"fmt"
	"os"
)

//声明文件写入器
type file_Writer struct {
	file *os.File
}

//设置文件写入器写入的文件名
func (f *file_Writer)Set_File(filename string)(err error)  {
	//如果文件已经打开,关闭前一个文件
	if f.file!=nil{
		f.file.Close()
	}
	//创建一个文件并保存文件句柄
	f.file,err=os.Create(filename)
	//如果创建的过程出现错误,则返回错误
	return err
}

//实现Log_Writer的Write()方法
func (f *file_Writer)Write(data interface{})error  {
	//日志文件可能没有创建成功
	if f.file==nil{
		//日志文件没有准备好
		return errors.New("file not created")
	}
	//将数据序列化为字符串
	str:=fmt.Sprintf("%v\n",data)
	//将数据以字节数组写入文件中
	_,err:=f.file.Write([]byte(str))
	return err
}
//创建文件写入器实例
func new_File_Writer()*file_Writer  {
	return &file_Writer{}
}

(3)命令行写入器

package main

import (
	"fmt"
	"os"
)

//命令行写入器
type console_Writer struct {

}

//实现Log_Writer的Write()方法
func (f *console_Writer)Write(data interface{})error  {
	//将数据序列化为字符串
	str:=fmt.Sprintf("%v\n",data)
	//将数据以字节数组写入命令行
	_,err:=os.Stdout.Write([]byte(str))
	return err
}

//创建命令行写入器实例
func new_Console_Writer()*console_Writer  {
	return &console_Writer{}
}

(4)使用日志

package main

import "fmt"

//创建日志器
func create_Logger()*Logger  {
	//创建日志器
	l:=New_Logger()
	//创建命令行写入器
	cw:=new_Console_Writer()
	//注册命令行写入器到日志器中
	l.Register_Writer(cw)
	//创建文件写入器
	fw:=new_File_Writer()

	//设置文件名
	if err:=fw.Set_File("log.log");err!=nil{
		fmt.Println(err)
	}
	//注册文件写入器到日志器中
	l.Register_Writer(fw)
	return l
}

func main()  {
	//准备日志器
	l:=create_Logger()
	//写日志
	l.Log("hello")
}

7.5示例:使用接口进行数据的排序

用户自定义类型排序,需要实现sort.Interface接口的Len,Less和Swap三个方法。

字符串排序

package main

import (
	"fmt"
	"sort"
)

//将[]string 定义为My_String_List类型
type My_String_List []string
//实现sort.Interface接口的获取元素数量的方法
func (m My_String_List)Len()int  {
	return len(m)
}
//实现sort.Interface接口的比较元素的方法
func (m My_String_List)Less(i,j int)bool  {
	return m[i]

Go语言中的sort包中定义的一些常见类型的排序方法

《Go语言从入门到进阶实战》学习笔记:第七章 接口_第1张图片

3、对结构体数据进行排序

package main

import (
	"fmt"
	"sort"
)

//声明英雄分类
type Hero_Kind int

//定义Hero_Kind常量,类似于枚举
const(
	None_Hero_Kind = iota
	Tank
	Assassin
	Mage
)

//定义英雄名单的结构
type Hero struct {
	Name string//英雄名
	Kind Hero_Kind//英雄种类
}

//将英雄指针的切片定义为Heros类型
type Heros []*Hero

//实现sort.Interface 接口取元素数量方法
func (s Heros)Len()int  {
	return len(s)
}
//实现sort.Interface 接口比较元素方法
func (s Heros)Less(i,j int)bool  {
	//如果英雄的没类不一致时,优先对分类进行排序
	if s[i].Kind!=s[j].Kind{
		return s[i].Kind

7.6接口的嵌套组合--将多个接口放在一个接口内

package main

import "io"

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

type Closer interface {
	Close()error
}
//两个接口嵌入一个接口中
type WriteCloser interface {
	Writer
	Closer
}

//声明一个设备
type device struct {

}

//实现Writer的Write方法
func (d *device)Write(p []byte)(n int,err error)  {
	return 0,nil
}
//实现Closer的Close方法
func (d *device)Close()error  {
	return nil
}

func main()  {
	//声明写入关闭器,并赋予device实例
	var wc io.WriteCloser = new(device)

	//写入数据
	wc.Write(nil)
	//关闭设备
	wc.Close()
	//声明写入器,并赋予device实例
	var writeOnly io.Writer = new(device)
	//写入数据
	writeOnly.Write(nil)
}


7.7在接口和类型间转换

1、类型断言格式

t:=i.(T)

i代表接口变量,T代表转换的目标类型,t代表转换后的变量。

更优的格式   t,ok:=i.(T)

如果接口未实现,则ok置为false,t置为T类型的0.

2、将接口转换为其他接口

实现某个接口的类型同时实现了另外一个接口,此时可以在两个接口间转换。

package main

import "fmt"

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

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

//定义鸟类
type bird struct {

}
//实现飞行动物接口
func (b *bird)Fly()  {
	fmt.Println("bird:fly")
}
//为鸟添加Walk方法,实现动物行走接口
func (b *bird)Walk()  {
	fmt.Println("bird:walk")
}
//定义猪类
type pig struct {

}

//为猪添加Walk方法,实现动物行走接口
func (p *pig)Walk()  {
	fmt.Println("pig:walk")
}

func main()  {
	//创建动物的名字到实例的映射
	animals:=map[string]interface{}{
		"bird":new(bird),
		"pig":new(pig),
	}
	//遍历映射
	for name,obj:=range animals{
		//判断对象是否为飞行动物
		f,isFlyer:=obj.(Flyer)
		//判断对象是否为行走动物
		w,isWalk:=obj.(Walker)

		fmt.Printf("name:%s is Flyer:%v isWalker: %v\n",name,isFlyer,isWalk)
		//如果是飞行动物则调用飞行动物接口
		if isFlyer{
			f.Fly()
		}
		//如果是行走动物则调用行走动物接口
		if isWalk{
			w.Walk()
		}
	}
}

7.8空接口类型(interface{})--能保存所有值得类型

var any interface{}

any=1
fmt.Println(any)

any="hello"
fmt.Println(any)

空接口类型转换为其他类型需要使用类型断言实现。

3、空接口值比较

(1)不同类型的空接口间的比较结果不相同

(2)不能比较空接口中的动态值,如切片等

类型的可比较性:

《Go语言从入门到进阶实战》学习笔记:第七章 接口_第2张图片

7.9示例:使用空接口实现可以保持任意值的字典

package main

import "fmt"

//字典结构
type Dictionary struct {
	data map[interface{}]interface{}
}

//根据键获取值
func (d *Dictionary)Get(key interface{})interface{}  {
	return d.data[key]
}

//设置键值
func (d *Dictionary)Set(key ,value interface{})  {
	d.data[key]=value
}

//遍历所有键值,如果回调返回false,停止遍历
func (d *Dictionary)Visit(callback func(k,v interface{})bool)  {
	if callback==nil{
		return
	}
	for k,v:=range d.data{
		if !callback(k,v){
			return
		}
	}
}

//清空所有数据
func (d *Dictionary)Clear()  {
	d.data=make(map[interface{}]interface{})
}
//创建一个字典

func New_Dictionary()*Dictionary  {
	d:=&Dictionary{}
	//初始化map
	d.Clear()
	return d
}
//使用字典
func main()  {
	//创建字典实例
	dict:=New_Dictionary()
	//添加游戏数据
	dict.Set("My Favorite",60)
	dict.Set("Terra Craft",36)
	dict.Set("Don't Hungry",24)

	//获取值并打印
	favorite:=dict.Get("My Favorite")
	fmt.Println("Favorite:",favorite)

	//遍历所有的字典元素
	dict.Visit(func(k, v interface{}) bool {
		//将值转为int,并判断是否大于40
		if v.(int)>40{
			//输出很贵
			fmt.Println(k,"is expensive")
			return true
		}
		//默认输出很便宜
		fmt.Println(k,"is cheap")
		return true
	})
}

7.10类型分支——批量判断空接口中变量的类型

1、类型断言的书写格式

switch 接口变量.(type){
    case 类型1:
        //变量是类型1时的处理
case 类型2:
        //变量是类型2时的处理
...
default:
    //变量不是所有case中列举类型时的处理
}

2、使用类型分支判断基本类型

package main

import "fmt"

func printType(v interface{}){

    switch v.(Type){
    case int:
        fmt.Println(v,"is int")
    case string:
        fmt.Println(v,"is string")
    case bool:
        fmt.Println(v,"is bool")
    }
}

func main(){
    printType(1024)
    printType("pig")
    printType(true)
}

3、使用类型分支判断接口类型

package main

import "fmt"

//电子支付方式
type Alipay struct {

}
//为Alipay添加CauUseFaceID()方法,表示电子支付方式支持刷脸
func (a *Alipay)CanUseFaceID()  {

}
//现金支付方式
type Cash struct {

}
//为Cash添加Stolen()方法,表示现金支付方式会出现偷窃情况
func (c *Cash)Stolen()  {

}
//具备刷脸特性的接口
type CantainCanUseFaceID interface {
	CanUseFaceID()
}

//具备偷窃特性的接口
type ContainStolen interface {
	Stolen()
}

//打印支付方式具备的特点
func print(payMethod interface{})  {
	switch payMethod.(type) {
	case CantainCanUseFaceID:
		fmt.Printf("%T can use faceid\n",payMethod)
	case ContainStolen:
		fmt.Printf("%T my be stolen\n",payMethod)
	}
}

func main()  {
	//使用电子支付判断
	print(new(Alipay))
	//使用现金判断
	print(new(Cash))
}

7.11示例:实现有限状态机(FSM)

(1)状态机中的状态间可以自由转换。

(2)每个状态可以设置它可以转移到的状态。

状态接口

package main

import "reflect"

//状态接口
type State interface {
	//获取状态名字
	Name()string

	//该状态是否允许同状态转移
	EnableSameTransit()bool

	//响应状态开始时
	OnBegin()
	//响应状态结束时
	OnEnd()

	//判断是否转移到某个状态
	CanTransitTo(name string)bool
}

//从状态实例获取状态名
func StateName(s State)string  {
	if s==nil{
		return "none"
	}

	//使用反射获取状态的名称
	return reflect.TypeOf(s).Elem().Name()
}

状态基本信息

//协助用户实现一些默认的实现
package main

//状态的基本信息和默认实现
type StateInfo struct {
	//状态名
	name string
}


//状态名
func (s *StateInfo)Name() string {
	return s.name
}

//提供给内部设置名字
func (s *StateInfo)setName(name string)  {
	s.name=name
}

//允许同状态转移
func (s *StateInfo)EnableSameTransit()bool  {
	return false
}

//默认将状态开启时实现
func (s *StateInfo)OnBegin()  {

}
//默认将状态结束时实现
func (s *StateInfo)OnEnd()  {

}

//默认可以转移到任何状态
func (s *StateInfo)CanTransitTo(name string)bool  {
	return true
}

状态管理器

package main

import "errors"

//状态管理器
type StateManager struct {
	//已添加的状态
	stateByName map[string]State

	//状态改变时的回调
	OnChange func(from, to State)
	//当前状态
	curr State
}

//添加一个状态到管理器
func (sm *StateManager)Add(s State){
	//获取状态名
	name:=StateName(s)

	//将s装换为能设置名字的接口,然后调用该接口
	s.(interface{
		setName(name string)
	}).setName(name)

	//根据状态名称获取已经添加的状态,检查该状态是否存在
	if sm.Get(name)!=nil{
		panic("duplicate state:"+name)
	}

	//根据名字保存到map中
	sm.stateByName[name]=s
}

//根据名字获取指定状态
func (sm *StateManager)Get(name string)State  {
	if v,ok:=sm.stateByName[name];ok{
		return v
	}
	return nil
}

//初始化状态管理器
func NewStateManager()*StateManager{
	return &StateManager{
		stateByName:make(map[string]State),
	}
}

状态间转移(和上边在同一个文件)

//状态没有找到的错误
var ErrStateNotFound = errors.New("state not found")

//禁止在同状态间转移
var ErrForbidSameStateTransit = errors.New("forbid same state transit")

//不能转移到指定状态
var ErrCannotTransitToState = errors.New("cannot transit to state")

//获取当前状态
func (sm *StateManager)CurrState()State  {
	return sm.curr
}

//当前状态能否转移到目标状态
func (sm *StateManager)CanCurrTransitTo(name string) bool {

	if sm.curr==nil{
		return true
	}
	//相同状态不用转换
	if sm.curr.Name()==name&&!sm.curr.EnableSameTransit(){
		return false
	}

	//使用当前状态,检查能否转移到指定名字的状态
	return sm.curr.CanTransitTo(name)
}

//转移到指定状态
func (sm *StateManager)Transit(name string)error{
	//获取目标状态
	next:=sm.Get(name)

	//目标不存在
	if next==nil{
		return ErrStateNotFound
	}

	//记录转移前状态
	pre:=sm.curr

	//当前有状态
	if sm.curr!=nil{
		//相同的状态不用转换
		if sm.curr.Name()==name&&!sm.curr.EnableSameTransit(){
			return ErrForbidSameStateTransit
		}

		//不能转移到目标状态
		if !sm.curr.CanTransitTo(name){
			return ErrCannotTransitToState
		}
		//结束当前状态
		sm.curr.OnEnd()
	}
	//将当前状态切换为要转移的目标状态
	sm.curr=next
	//调用新状态的开始
	sm.curr.OnBegin()

	//通知回调
	if sm.OnChange!=nil{
		sm.OnChange(pre,sm.curr)
	}
	return nil
}

自定义状态实现状态接口

package main

import "fmt"

//闲置状态
type IdleState struct {
	StateInfo  //使用StateInfo实现基础接口

}

//重新实现状态开始
func (i *IdleState)OnBegin()  {
	fmt.Println("IdleState begin")
}

//重新实现状态结束
func (i *IdleState)OnEnd()  {
	fmt.Println("IdleState end")
}

//移动状态
type MoveState struct {
	StateInfo
}

func (m *MoveState)OnBegin()  {
	fmt.Println("MoveState begin")
}

//允许移动状态相互转换
func (m *MoveState) EnableSameTransit() bool {
	return true
}

//跳跃状态
type JumpState struct {
	StateInfo
}

func (j *JumpState)OnBegin()  {
	fmt.Println("JumpState begin")
}

//跳跃状态不能转移到移动状态
func (j *JumpState)CanTransitTo(name string)bool{
	return name != "MoveState"
}

使用状态机

//使用状态机

func main()  {
	//实例化一个状态管理器
	sm:=NewStateManager()

	//响应状态转移的通知
	sm.OnChange= func(from, to State) {

		//打印状态转移的流向
		fmt.Printf("%s---> %s\n\n",StateName(from),StateName(to))
	}

	//添加3个状态
	sm.Add(new(IdleState))
	sm.Add(new(MoveState))
	sm.Add(new(JumpState))

	//在不同状态间转移
	transitAndReport(sm,"IdleState")

	transitAndReport(sm,"MoveState")

	transitAndReport(sm,"MoveState")

	transitAndReport(sm,"JumpState")

	transitAndReport(sm,"JumpState")

	transitAndReport(sm,"IdleState")
}

//封装转移状态和输出日志
func transitAndReport(sm *StateManager,target string)  {
	if err:=sm.Transit(target);err!=nil{
		fmt.Printf("FAILED! %s -->%s,%s\n\n",sm.CurrState().Name(),target,err.Error())
	}
}

 

你可能感兴趣的:(Go语言)