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) StringSlice sort.Strings(a []string) 字符ASCII值升序
整形(int) IntSlice sort.Ints(a []int) 数值升序
双精度浮点(float64) Float64Slice sort.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)

一、状态的概念

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

三、状态基本信息

四、状态管理

五、在状态间转移

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

七、使用状态机

你可能感兴趣的:(Go语言,go)