Go语言的接口

 使用接口:

接口声明的格式形式代码如下:

type 接口类型名 interface{

        方法名1 ( 参数列表1 ) 返回值列表1

        方法名2 ( 参数列表2 ) 返回值列表2

        ……

}

接口类型名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer,有关闭功能的接口叫Closer等。

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

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

goroutine和channel是支撑起Go语言的并发模型的基石。

格式定义接口,如下:

type Namer interface{

        Method1(param_list) return_type

        Method2(param_list) return_type

        ...

}

Namer是一个接口类型。

使用接口Robot,程序清单如下:

package main

import(
  "fmt"
  "errors"
)
type Robot interface{
  PowerOn() error
}
type T850 struct{
  Name string
}
func(a *T850)PowerOn()error{
  return nil
}
type R2D2 struct{
  Broken bool
}
func (r *R2D2)PowerOn()error{
  if r.Broken{
    return errors.New("R2D2 is broken")
  }else{
    return nil
  }
}
func Boot(r Robot)error{
  return r.PowerOn()
}
func main(){
  t:=T850{
    Name:"The Terminator",
  }
  r:=R2D2{
    Broken:true,
  }
  err:=Boot(&r)
  if err!=nil{
    fmt.Println(err)
  }else{
    fmt.Println("Robot is powered on!")
  }
  err=Boot(&t)
  if err!=nil{
    fmt.Println(err)
  }else{
    fmt.Println("Robot is powered on!")
  }
}

运行结果如下:

R2D2 is broken
Robot is powered on!

代码如下:

package main

import (
	"fmt"
)

type Shaper interface {
	Area() float32
}

type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func main() {
	sq1 := new(Square)
	sq1.side = 5
	areaIntf := sq1
	fmt.Printf("面积为: %f\n", areaIntf.Area())
}

运行结果如下:

面积为: 25.000000

代码如下:

package main

import (
	"fmt"
)

type Shaper interface {
	Area() float32
}

type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

type Rectangle struct {
	length, width float32
}

func (r Rectangle) Area() float32 {
	return r.length * r.width
}

func main() {
	r := Rectangle{5, 3}
	q := &Square{5}
	shapes := []Shaper{r, q}
	for n, _ := range shapes {
		fmt.Println("形状参数:", shapes[n])
		fmt.Println("形状面积是:", shapes[n].Area())
	}
}

运行结果如下:

形状参数: {5 3}
形状面积是: 15
形状参数: &{5}
形状面积是: 25

代码如下:

package main

import (
	"fmt"
)

type stockPosition struct {
	ticker     string
	sharePrice float32
	count      float32
}

func (s stockPosition) getValue() float32 {
	return s.sharePrice * s.count
}

type car struct {
	make  string
	model string
	price float32
}

func (c car) getValue() float32 {
	return c.price
}

type valuable interface {
	getValue() float32
}

func showValue(asset valuable) {
	fmt.Printf("资产的价值是: %f\n", asset.getValue())
}

func main() {
	var o valuable = stockPosition{"GOOG", 577.20, 4}
	showValue(o)
	o = car{"BMW", "M3", 66500}
	showValue(o)
}

运行结果如下:

资产的价值是: 2308.800049
资产的价值是: 66500.000000

接口类型和约定

1.动态类型

代码如下:(没看懂)

package main

import (
	"fmt"
	"math"
)

type Square struct {
	side float32
}

type Circle struct {
	radius float32
}

type Shaper interface {
	Area() float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (ci *Circle) Area() float32 {
	return ci.radius * ci.radius * math.Pi
}

func main() {
	var areaIntf Shaper
	sq1 := new(Square)
	sq1.side = 5

	areaIntf = sq1
	if t, ok := areaIntf.(*Square); ok {
		fmt.Printf("areaIntf的类型是:%T\n", t)
	}
	if u, ok := areaIntf.(*Circle); ok {
		fmt.Printf("areaIntf的类型是:%T\n", u)
	} else {
		fmt.Println("areaIntf不含类型为Circle的变量")
	}
}

运行结果如下:

areaIntf的类型是:*main.Square
areaIntf不含类型为Circle的变量

2.类型判断

接口实现

嵌套接口

接口赋值

接口查询

接口组合

实现接口的条件:

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

代码如下:

package main

import (
	"fmt"
)

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

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

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

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

运行结果如下:

WriteData: data

条件二:接口中所有方法均被发现

理解类型与接口的关系

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

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

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

1、日志对外接口

日志写入器,logger.go的代码如下:

package main

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

//日志器
type Logger struct {
	//这个日志器用到的日志写入器
	writerList []LogWriter
}

//注册一个日志写入器
func (l *Logger) RegisterWriter(writer LogWriter) {
	l.writerList = append(l.writerList, writer)
}

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

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

2、文件写入器

file.go的代码如下:

package main

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

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

//设置文件写入器写入的文件名
func (f *fileWriter) SetFile(filename string) (err error) {
	//如果文件已经打开,关闭前一个谁的
	if f.file != nil {
		f.file.Close()
	}
	//创建一个文件并保存文件句柄
	f.file, err = os.Create(filename)

	//如果创建的过程出现错误,则返回错误
	return err
}

//实现LogWriter的Write()方法
func (f *fileWriter) 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 newFileWriter() *fileWriter {
	return &fileWriter{}
}

3、命令行写入器

console.go的代码如下:

package main

import (
	"fmt"
	"os"
)

//命令行写入器
type consoleWriter struct {
}

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

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

4、使用日志

main.go的代码如下:

package main

import "fmt"

func createLogger() *Logger {
	//创建日志器
	l := NewLogger()
	//创建命令行写入器
	cw := newConsoleWriter()
	//注册命令行写入器到日志器中
	l.RegisterWriter(cw)
	//创建文件写入器
	fw := newFileWriter()
	//设置文件名
	if err := fw.SetFile("log.log"); err != nil {
		fmt.Println(err)
	}
	//注册文件写入器到日志器中
	l.RegisterWriter(fw)
	return 1
}

func main() {
	//准备日志器
	l := createLogger()
	//写一个日志
	l.Log("Hello")
}

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

一、使用sort.Interface接口进行排序

sortstring.go,字符串排序,代码如下:

package main

import (
	"fmt"
	"sort"
)

//将[]string定义为MyStringList类型
type MyStringList []string

//实现sort.Interface接口的获取元素数量方法
func (m MyStringList) Len() int {
	return len(m)
}

//实现sort.Interface接口的比较元素方法
func (m MyStringList) Less(i, j int) bool {
	return m[i] < m[j]
}

//实现sort。Interface接口的交换元素方法
func (m MyStringList) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

func main() {
	//准备一个内容被打乱顺序的字符串切片
	names := MyStringList{
		"3. Triple Kill",
		"5. Penta Kill",
		"2. Double Kill",
		"4. Quadra Kill",
		"1. First Blood",
	}

	//使用sort包进行排序
	sort.Sort(names)

	//遍历打印结果
	for _, v := range names {
		fmt.Printf("%s\n", v)
	}
}

运行结果如下:

1. First Blood
2. Double Kill
3. Triple Kill
4. Quadra Kill
5. Penta Kill

二、常见类型的便捷排序

1、字符串切片的便捷排序

2、对整形切片进行排序

3、sort包内建的类型排序接口一览

sort包中内建的类型排序接口
类型实现sort.Interface的类型直接排序方法说明
字符口中(String)StringSlicesort.Strings(a []string)字符ASCII值升序
整形(int)IntSlicesort.Ints(a []int)数值升序
双精度浮点(float64)Float64Slicesort.Float64s(a []float64)数值升序

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

1、完整实现sort.Interface进行结构体排序

代码如下:

package main

import (
	"fmt"
	"sort"
)

//声明英雄的分类
type HeroKind int

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

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

//将英雄指针的切片定义为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 < s[j].Kind
	}
	//默认按英雄名字字符升序排列
	return s[i].Name < s[j].Name
}

//实现sort.Interface接口交换元素方法
func (s Heros) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func main() {

	//准备英雄列表
	heros := Heros{
		&Hero{"吕布", Tank},
		&Hero{"李白", Assassin},
		&Hero{"妲己", Mage},
		&Hero{"貂蝉", Assassin},
		&Hero{"关羽", Tank},
		&Hero{"诸葛亮", Mage},
	}

	//使用sort包进行排序
	sort.Sort(heros)

	//遍历英雄列表打印排序结果
	for _, v := range heros {
		fmt.Printf("%+v\n", v)
	}
}

运行结果如下:

&{Name:关羽 Kind:1}
&{Name:吕布 Kind:1}
&{Name:李白 Kind:2}
&{Name:貂蝉 Kind:2}
&{Name:妲己 Kind:3}
&{Name:诸葛亮 Kind:3}

2、使用sort.Slice进行切片元素排序

代码如下:

package main

import (
	"fmt"
	"sort"
)

//声明英雄的分类
type HeroKind int

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

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

func main() {

	//准备英雄列表
	heros := []*Hero{
		&Hero{"吕布", Tank},
		&Hero{"李白", Assassin},
		&Hero{"妲己", Mage},
		&Hero{"貂蝉", Assassin},
		&Hero{"关羽", Tank},
		&Hero{"诸葛亮", Mage},
	}

	sort.Slice(heros, func(i, j int) bool {
		if heros[i].Kind != heros[j].Kind {
			return heros[i].Kind < heros[j].Kind
		}
		return heros[i].Name < heros[j].Name
	})

	//遍历英雄列表打印排序结果
	for _, v := range heros {
		fmt.Printf("%+v\n", v)
	}
}

运行结果如下:

&{Name:关羽 Kind:1}
&{Name:吕布 Kind:1}
&{Name:李白 Kind:2}
&{Name:貂蝉 Kind:2}
&{Name:妲己 Kind:3}
&{Name:诸葛亮 Kind:3}

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

在接口和类型间转换

一、类型断言的格式

类型断言的基本格式如下:

t:=i.(T)

i代表接口变量

T代表转换的目标类型

t代表转换后的变量

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

鸟与猪,代码如下:

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, isWalker := obj.(Walker)

		fmt.Printf("name: %s isFlyer:%v isWalker: %v\n", name, isFlyer, isWalker)

		//如果是飞行动物则调用飞行动物接口
		if isFlyer {
			f.Fly()
		}
		//如果是行走动物则调用行走动物接口
		if isWalker {
			w.Walk()
		}
	}
}

运行结果如下:

name: bird isFlyer:true isWalker: true
bird: fly
bird: walk
name: pig isFlyer:false isWalker: true
pig: walk

书上的运行结果如下:

name: pig isFlyer:false isWalker: true
pig: walk
name: bird isFlyer:true isWalker: true
bird: fly
bird: walk

三、将接口转换为其它类型

空接口类型()——能保存所有值的类型

一、将值保存到空接口

代码如下:

package main

import (
	"fmt"
)

func main() {
	var any interface{}

	any = 1
	fmt.Println(any)

	any = "hello"
	fmt.Println(any)

	any = false
	fmt.Println(any)
}

运行结果如下:

1
hello
false

二、从空接口获取值

代码如下:

package main

import (
	"fmt"
)

func main() {
	//声明a变量,类型int,初始值为1
	var a int = 1
	//声明i变量,类型为interface{},初始值为a,此时i的值变为1
	var i interface{} = a
	//声明b变量,尝试赋值i
	var b int = i.(int)
	fmt.Println(b)
}

运行结果如下:

1

三、空接口的值比较

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

代码如下:

package main

import (
	"fmt"
)

func main() {
	//a保存整形
	var a interface{} = 100
	//b保存字符串
	var b interface{} = "hi"
	//两个空接口不相等
	fmt.Println(a == b)
}

运行结果如下:

false

2、不能比较空接口中的动态值

类型的可比较性
类型说明
map宕机错误,不可比较
切片([]T)宕机错误,不可比较
通道(channel)可比较,必须由同一个make生成,也就是同一个通道才会是true,否则为false
数组([容量]T)可比较,编译期知道两个数组是否一致
结构体可比较,可以逐个比较结构体的值
函数可比较

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

代码如下:

package main

import (
	"fmt"
)

//字典结构
type Dictionary struct {
	data map[interface{}]interface{} //键值都为interface{}类型
}

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

//设置键值
func (d *Dictionary) Set(key interface{}, 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 NewDictionary() *Dictionary {
	d := &Dictionary{}
	//初始化map
	d.Clear()
	return d
}

func main() {
	//创建字典实例
	dict := NewDictionary()

	//添加游戏数据
	dict.Set("My Factory", 60)
	dict.Set("Terra Craft", 36)
	dict.Set("Don't Hungry", 24)

	//获取值及打印值
	favorite := dict.Get("Terra Craft")
	fmt.Println("favorite:", favorite)

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

运行结果如下:

favorite: 36
My Factory is expensive
Terra Craft is cheap
Don't Hungry is cheap

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

一、类型断言的书写格式

switch实现类型分支时的写法格式如下:

switch 接口变量.(type){

        case 类型1:

                //变量是类型1时的处理

        case 类型2:

                //变量是类型2时的处理

……

default:

                //变量不是所有case中列举的类型时的处理

}

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

代码如下:

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

运行结果如下:

1024 is int
pig is string
true is bool

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

代码如下:

package main

import (
	"fmt"
)

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

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

}

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

//Cash添加Stolen()方式,表示现金支付方式会出现 偷窥情况
func (a *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 may be stolen\n", payMethod)
	}
}

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

	//使用现金判断
	print((new(Cash)))
}

运行结果如下:

*main.Alipay can use faceid
*main.Cash may be stolen

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

一、状态的概念

二、自定义状态需要实现的接口

状态接口,state.go,代码如下:

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

三、状态基本信息

状态信息,info.go,代码如下:

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
}

四、状态管理

状态管理器,statemgr.go,代码如下:

package main

import "errors"

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

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

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

// 状态管理器
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 (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
}

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

五、在状态间转移

六、自定义状态实现状态接口

一系列状态实现,main.go,代码如下:

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

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")
}

七、使用状态机

运行结果如下:

PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\fsm> go run main.go
# command-line-arguments
.\main.go:9:2: undefined: StateInfo
.\main.go:24:2: undefined: StateInfo
.\main.go:38:2: undefined: StateInfo
.\main.go:51:27: undefined: StateManager
.\main.go:59:8: undefined: NewStateManager
.\main.go:62:30: undefined: State
.\main.go:65:32: undefined: StateName
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\fsm>

代码如下:

package main

import (
	"fmt"
)

type A struct {
	Books int
}

type B interface {
	f()
}

func (a A) f() {
	fmt.Println("A.f() ", a.Books)
}

type I int

func (i I) f() {
	fmt.Println("I.f() ", i)
}

func main() {
	var a A = A{Books: 9}
	a.f()

	var b B = A{Books: 99} // 接口类型可接受结构体A的值,因为结构体A实现了接口
	b.f()

	var i I = 199 // I是int类型引申出来的新类型
	i.f()

	var b2 B = I(299) // 接口类型可接受新类型I的值,因为新类型I实现了接口
	b2.f()
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
A.f()  9
A.f()  99
I.f()  199
I.f()  299

[Done] exited with code=0 in 8.338 seconds

代码如下:

package main

import (
	"fmt"
)

type I interface {
	f()
}

type T string

func (t T) f() {
	fmt.Println("T Method")
}

type Stringer interface {
	String() string
}

func main() {

	// 类型断言
	var varI I
	varI = T("Tstring")
	if v, ok := varI.(T); ok { // 类型断言
		fmt.Println("varI类型断言结果为:", v) // varI已经转为T类型
		varI.f()
	}

	// Type-switch做类型判断
	var value interface{} // 默认为零值

	switch str := value.(type) {
	case string:
		fmt.Println("value类型断言结果为string:", str)

	case Stringer:
		fmt.Println("value类型断言结果为Stringer:", str)

	default:
		fmt.Println("value类型不在上述类型之中")
	}

	// Comma-ok断言
	value = "类型断言检查"
	str, ok := value.(string)
	if ok {
		fmt.Printf("value类型断言结果为:%T\n", str) // str已经转为string类型
	} else {
		fmt.Printf("value不是string类型 \n")
	}
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
varI类型断言结果为: Tstring
T Method
value类型不在上述类型之中
value类型断言结果为:string

[Done] exited with code=0 in 12.094 seconds

代码如下:

运行结果如下:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值