zoukankan      html  css  js  c++  java
  • 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: *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: *file does not implement DataWriter (wrong type for WriteData method) have WriteData(int) error want 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: *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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", payMethod)
      case ContainStolen:
        fmt.Printf("%T may be stolen
    ", 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 
    ", 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
    
    ", 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

  • 相关阅读:
    2017.5.11下午学习内容
    windows消息和消息队列
    探索Win32系统之窗口类(转载)
    WinMain函数详解(转载)
    Ajax爬取实战头条街拍美图
    Ajax实战微博
    Ajax请求分析实战
    ubuntu 安装rails
    ubuntu Thunderbird 接收邮件显示乱码的问题排除
    ubuntu 开机挂载windows分区
  • 原文地址:https://www.cnblogs.com/smallyi/p/12543401.html
Copyright © 2011-2022 走看看