GO语言 -- 第7章 接口(INTEFACE)

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

实现失败例子:

  1. 函数名不一致导致报错

​
...
// 修改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)",
...
}
  1. 实现接口的方法签名不一致导致的报错

    把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. 日志对外接口

  2. 文件写入器

  3. 命令行写入器

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

 

  1. 文件写入器 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{}
}

  

  1. 命令行写入器

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

  

结果:

  1. First Kill

  2. Double Kill

  3. Triple Kill

  4. Quadra Kill

  5. Penta Kill

7.5.2 创建类型的便捷排序

  1. 字符串切片

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

  

  1. 整形切片排序

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

  

  1. 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 结构体数据排序

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

  1. 使用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 接口的嵌套组合--将多个接口放在一个接口内

接口与接口嵌套组合成新接口

  • 只有接口所有方法被实现,则这个接口的所有嵌套接口的方法均可以被调用

  1. 系统包中的接口嵌套组合

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
}
  1. 代码中使用接口嵌套组合

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 空接口值比较

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

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

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

// 不同类型空接口结果比较
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 示例:使用空接口实现保存任意值的字典

  1. 值设置和获取

  2. 遍历字段的所有键值关联数据

  3. 初始化和清除

  4. 使用字典

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)表示有限个状态及在这些状态间的转移和行为的数学模型

例子目的:实现状态接口、状态管理器及一系列的状态和使用状态的逻辑

  1. 自定义状态需求实现的接口

  2. 状态信息

  3. 状态管理

  4. 在状态间转移

  5. 自定义状态实现状态接口

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

  

  1. 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 
}

  

  1. 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 JumpState ---> IdleState

posted @ 2020-03-21 23:33  洪先森  阅读(370)  评论(0编辑  收藏  举报