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
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号