GO语言 -- 第7章 接口(INTEFACE)
7.1 声明接口
接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式,类型及结构
7.1.1 接口声明的格式
格式:
type 接口类型名 interface {
方法名1(参数列表)返回值列表1
方法名2(参数列表)返回值列表2
...
}
-
接口类型名:命名时会在命名单词后加
er
, eg: Writer -
方法名: 当方法首字母大写时,接口类型名首字母大写时,这个方法可以被接口所在包(package)之外的代码访问
-
参数列表、返回值列表:列表中的参数变量名可以被忽略
type Write interface {
Write([]byte) error
}
7.2 实现接口的条件
接口定义后,需要实现接口,调用方才能正确编译并使用接口
接口实现遵循2条规则
7.2.1 被实现条件一:接口的方法与实现接口的类型方法格式一致
DataWriter interface{} 里面WriteData(data interface{}) error方法 要与 实现的 func (d *file) WriteData(data interface{}) error {} 方法名称、参数列表、返回参数列表
中任意一项要一致,否则不会被实现
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") }
实现失败例子:
-
函数名不一致导致报错
... // 修改file方法DataWriterX() func (d *file) WriteDataX(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") }
报错:
{ ...
"message": "cannot use f (type *file) as type DataWriter in assignment:\n\t*file does not implement DataWriter (missing WriteData method)",
...
}
-
实现接口的方法签名不一致导致的报错
把data参数的类型从interface{}修改为int类型
// 实现DataWriter接口WriteData()方法 func (d *file) WriteData(data int) error { //模拟写入数据 fmt.Println("WriteData:", data) return nil }
编译报错:
{
...
"message": "cannot use f (type *file) as type DataWriter in assignment:\n\t*file does not implement DataWriter (wrong type for WriteData method)\n\t\thave WriteData(int) error\n\t\twant WriteData(interface {}) error",
...
}
7.2.2 条件二:接口所以方法均被实现
//定义一个数据写入盘 type DataWriter interface { WriteData(data interface{}) error CanWrite() bool }
报错:
{
...
"message": "cannot use f (type *file) as type DataWriter in assignment:\n\t*file does not implement DataWriter (missing CanWrite method)",
...
}
go接口实现时隐式,无须让实现接口的类型写出实现了哪些接口,这种设计称为非侵入式设计,让实现者所有类型均时平行的
7.3 理解类型与接口的关系
类型与接口之间有一对多
和多对一
的关系
7.3.1 一个类型可以实现多个接口
比如:定义一个socket 结构体类型,可以实现 i o.Writer() 接口和io.Close()方法,这两个接口彼此独立,都不知道对方的实现
package main import ("io") type Socket struct{ } func (s *Socket) Write(p []byte) (n int, err error) { return 0, nil } func (s *Socket) Close() error { return nil } // io 包接口 type Write interface { Write(p []byte) (n int, err error) } // io 包接口 type Closer interface{ Close() error } func usingWriter(writer io.Writer) { writer.Write(nil) } func usingCloser(closer io.Closer) { closer.Close() } func main() { //实例化Socket s := new(Socket) usingWriter(s) usingCloser(s) }
7.3.1 多个类型可以实现相同接口
接口的方法不一定需要一个类型完全实现
接口的方法可以通过在类型中嵌入其他类型
或结构体
来实现
不需要知道接口的方法是通过一个类型完全实现,还是通过多个结构嵌入到一个结构体中拼凑共同实现
例子:
package main
import(
"fmt"
)
//一个服务器需要满足开启和写日志的功能
type Service interface{
Start()
Log(string)
}
//-------------------------------------
// 日志器
type Logger struct{
}
//实现Service的Log方法
func (g *Logger) Log(l string){
fmt.Printf("打印游戏日志:%s\n", l);
}
//-------------------------------------
//游戏服务
type GameService struct{
//嵌套Logger日志器
Logger
}
//实现Service Start()方法
func (g *GameService) Start() {
fmt.Println("游戏服务开始!!")
}
func main() {
var s Service = new(GameService)
s.Start()
s.Log("hello")
}
//游戏服务开始!!
//打印游戏日志:hello
7.4 示例:便于扩展输出方式的日志系统
-
日志对外接口
-
文件写入器
-
命令行写入器
-
使用日志
1.日志对外接口 Logger.go
package main // 声明日志写入器接口 type LogWriter interface{ Write(data interface{}) error } type Logger struct{ // 日志器用到日志写入器 writeList []LogWriter } // 注册日志写入器 func (l *Logger) RegisterWriter(writer LogWriter) { l.writeList = append(l.writeList, writer) } //讲一个data类型到数据写入到日志中 func (l *Logger) Log(data interface{}) { // 遍历所有组册到写入器 for _,writer := range l.writeList { writer.Write(data) } } //创建日期器到实例 func NewLogger() *Logger { return &Logger{} }
-
文件写入器 file.go
package main import ( "fmt" "errors" "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{} }
-
命令行写入器
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. 使用日志
package main import ( "fmt" ) //创建日志器 func creatLogger() *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 l } func main() { //准备日志器 l := creatLogger() //写一个日志 l.Log("test log") }
7.5 示例:使用接口进行数据的排序
接口定义排序格式:
type Interface interface {
//获取元素数量
Len() int
// 小于比较, 提供i,j两个为元素索引
Less(i,j int) bool
// 交换元素
Swap(i,j int)
}
7.5.1 sort.interface接口排序
package main import ( "fmt" "sort" ) // 将[]string定义为MyStringList类型 type MyStringList []string // 获取元素数据量 func (m MyStringList) Len() int { return len(m) } // 比较元素 func (m MyStringList) Less(i, j int) bool { return m[i] < m[j] } // 交换元素 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 Kill", } // 使用sort包排序 sort.Sort(names) // 遍历打印 for _, v := range names { fmt.Printf("%s\n", v) } }
结果:
-
First Kill
-
Double Kill
-
Triple Kill
-
Quadra Kill
-
Penta Kill
7.5.2 创建类型的便捷排序
-
字符串切片
package main import( "fmt" "sort" ) type StringSlice []string func (p StringSlice) Len() int { return len(p) } func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] } func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func main() { //准备一个内容被打乱顺序的字符串切片 names := sort.StringSlice{ "3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Kill", } // 使用sort包排序 sort.Sort(names) // 遍历打印 for _, v := range names { fmt.Printf("%s\n", v) } }
-
整形切片排序
package main import( "fmt" "sort" ) type IntSlice []string func (p IntSlice) Len() int { return len(p) } func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] } func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func main() { //准备一个内容被打乱顺序的字符串切片 names := []string{ "3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Kill", } // 使用sort包排序 sort.Strings(names) // 遍历打印 for _, v := range names { fmt.Printf("%s\n", v) } }
-
sort包内建的类型排序接口
类型 | 实现sort.Interface的类型 | 直接排序方法 | 说明 |
---|---|---|---|
字符串(String) | StringSlice | Sort.Strings(a []string) | 字符ASCII升序 |
整形(Integer) | IntSlice | Sort.Ints(a []int) | 数值升序 |
双精度浮点(float64) | Float64Slice | Sort.Float64s(a []float64) | 数值升序 |
7.5.3 结构体数据排序
-
使用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 } 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}
-
使用sort.Slice 进行切片元素排序
Go语言 sort 包提供sort.Slice()函数进行切片排序
定义:
func Slice(slice interface{}, less func{i, j int} bool) package main import( "fmt" "sort" ) // 声明英雄分类 type HeroKind int // 定义HeroKind常量,类似枚举 const ( None = iota Tank Assassin Mage ) // 定义英雄名单结构 type Hero struct { Name string //名字 Kind HeroKind //种类 } func main() { //英雄列表 heros := []*Hero{ {"吕布", Tank}, {"李白", Assassin}, {"妲己", Mage}, {"貂蝉", Assassin}, {"关羽", Tank}, {"诸葛亮", Mage}, } // 使用sort包排序 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}
7.6 接口的嵌套组合--将多个接口放在一个接口内
接口与接口嵌套组合成新接口
-
只有接口所有方法被实现,则这个接口的所有嵌套接口的方法均可以被调用
-
系统包中的接口嵌套组合
go语言中i o包定义了写入器(Writer)\关闭器(Closer)和写入关闭器(WriteCloser)3个接口
type Write interface{
Write(p []byte) (n int, err error)
}
type Closer interface{
Close() error
}
type WriteCloser interface{
Writer
Closer
}
-
代码中使用接口嵌套组合
package main import("io") // 声明一个设备结构 type device struct{} // 实现io.Writer()方法 func (d *device) Write(p []byte) (n int, err error ) { return 0, nil } // 实现io.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) }
var wc io.WriteCloser = new(device) 由于 device 实现了io.WriteCloser()方法,所有device指针会被隐式转为io.WriteCloser()接口
7.7 在接口和类型中转换
GO语言使用接口断言(type asseritions)将接口转为另一接口、另外的类型
7.7.1 类型断言的格式
类型断言格式:
t := i.(T)
-
i 接口变量
-
T 代表转换的目标类型
-
t代表转换后的变量
注意:如果i没有完全实现T接口方法、会导致宕机 ,可以改成下面写法:
t, ok := i.(T)
7.7.2 将接口转换为其他接口
鸟和猪各种实现飞行动物、行走动物接口例子:
package main import "fmt" // 定义飞行动物接口 type Flyer interface{ Fly() } // 定义行走动物接口 type Walker interface{ Walk() } // 定义鸟类 type bird struct{ } // 鸟类实现飞行接口 func (b *bird) Fly() { fmt.Println("bird: fly") } // 定义猪类 type pig struct{ } // 鸟类实现飞行接口 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: false bird: fly name: pig isFlyer: false isWalker: true pig: walk
7.7.3 将接口转换为其他类型
// 由于pig 实Walker接口,所有被隐式转换为Walker接口类型
p1 := new(pig) var a Walker = p1 p2 := a.(*pig) p3 := a fmt.Printf("p1=%p p2=%p p3=%p\n", p1, p2, p3) //p1=0x118ffd0 p2=0x118ffd0 p3=0x118ffd0
7.8 空接口类型(interface{})--能保存所有值的类型
空接口是一种非常灵活的数据抽象和使用方法 任何值都能满足接口的需求
7.8.1 将值保存到空接口
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
7.8.2 从空接口获取值
保存到空接口的值,如果直接取出指定类型的值时,会发生变异错误
// 声明i变量 var a int = 1 var i interface{} = a var b int = i fmt.Println(b) //command-line-arguments //seven/7.8/nullInterface.go:20:6: cannot use i (type interface {}) as type int in //assignment: need type assertion // 声明i变量 var a int = 1 var i interface{} = a // 需要将i 进行断言 var b int = i.(int) fmt.Println(b)
7.8.3 空接口值比较
-
类型不同的空接口间的比较结果不相同
-
不能比较空接口中的动态值
类型不同的空接口间的比较结果不相同
// 不同类型空接口结果比较
var a1 interface{} = 100
var b1 interface{} = "hi"
fmt.Println(a1 == b1)
// false
不能比较空接口中的动态值
// 不能比较空接口的动态值
var c interface{} = []int{10}
var d interface{} = []int{20}
fmt.Println(c == d)
//panic: runtime error: comparing uncomparable type []int
类型的可可比性
类型 | 说明 |
---|---|
map | 宕机 |
切片([]T) | 宕机 |
通道(channel) | 可比较 |
通道(channel) | 可比较 |
结构体 | 可比较 |
函数 | 可比较 |
7.9 示例:使用空接口实现保存任意值的字典
-
值设置和获取
-
遍历字段的所有键值关联数据
-
初始化和清除
-
使用字典
dict.go
package main import "fmt" // 1.设置值和获取 type Dictionary struct{ data map[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 } // 2.遍历字段的所有键值关联数据 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 } } } // 3.初始化和清除 func (d *Dictionary) Clear() { d.data = make(map[interface{}]interface{}) } // 创建一个字典 func NewDictionary() *Dictionary { d := &Dictionary{} //初识化 d.Clear() return d } func main() { // 创建字典 dict := NewDictionary() // 添加数据 dict.Set("My Factory", 60) dict.Set("Terra craft", 30) dict.Set("Don`t Hugry", 23) // 获取打印 favorite := dict.Get("My Factory") 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: 60 Terra craft is cheap Don`t Hugry is cheap My Factory is expensive
7.10 类型分支--批量判断空接口中变量的类型
go语言switch 特殊用途:判断一个接口内保存 或实现的类型
7.10.1 类型断言的书写格式
switch 接口变量.(type) {
case 类型1:
//变量类型1时处理
case 类型2:
//变量类型2时处理
default:
//变量默认处理
}
7.10.2 使用类型分支判断基本类型
package main import "fmt" // 打印类型 func printType(v interface{}) { switch v.(type) { case int: fmt.Println(v, "v is int") case string: fmt.Println(v, "v is string") case bool: fmt.Println(v, "v is bool") } } func main() { printType(1024) printType("hello go") printType(true) }
结果: 1024 v is int hello go v is string true v is bool
7.10.3 使用类型分支判断接口类型
package main import "fmt" // 定义电子支付方式 type Alipay struct{} // alipay添加支持刷脸 func (a *Alipay) CanUseFaceID() { } //定义现金方式 type Cash struct{} //添加被偷窃方法 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 may be stolen\n", payMethod) } } func main() { //使用电子支付判断 print(&Alipay{}) //使用现金支付 print(&Cash{}) }
结果: *main.Alipay can use faceid *main.Cash may be stolen
7.11 示例:实现有限状态机(FSM)
有限状态机(Finite-Sate Machine, FSM)表示有限个状态及在这些状态间的转移和行为的数学模型
例子目的:实现状态接口、状态管理器及一系列的状态和使用状态的逻辑
-
自定义状态需求实现的接口
-
状态信息
-
状态管理
-
在状态间转移
-
自定义状态实现状态接口
-
seven/7.11/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() }
-
seven/7.11/fsm/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 }
3.seven/7.11/fsm/statemgr.go
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 no found") // 禁止在同状态转移 var ErrForbidSameStateStransit = 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 ErrForbidSameStateStransit } // 不能转移到目标状态 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 }
-
seven/7.11/fsm/main.go
package main import ( "fmt" ) //闲置状态 type IdleState struct { StateInfo //使用SateInfo实现基础接口 } // 重新实现状态开发 func (i *IdleState) OnBegin() { fmt.Println("IdleState begin") } // 重新实现状态结束 func (i *IdleState) OnEnd() { fmt.Println("IdleState end") } //移动状态 type MoveState struct { StateInfo //使用SateInfo实现基础接口 } // 重新实现状态开发 func (m *MoveState) OnBegin() { fmt.Println("MoveState begin") } func (m *MoveState) EnableSameTransit() bool { return true } //跳跃状态 type JumpState struct { StateInfo //使用SateInfo实现基础接口 } // 重新实现状态开发 func (j *JumpState) OnBegin() { fmt.Println("JumpSate 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", 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()) } }
结果:
IdleState begin none ---> IdleState IdleState end MoveState begin IdleState ---> MoveState MoveState begin MoveState ---> MoveState JumpSate begin MoveState ---> JumpState FAILED! JumpState --> JumpState, forbid same state transit
IdleState begin