便于扩展输出方式的日志系统
日志可以用于查看和分析应用程序的运行状态。日志一般可以支持输出多种形式,如命令行、文件、网络。
本例将搭建一个支持多种写入器的日志系统。
package main
import (
"errors"
"fmt"
"os"
)
/*
本例中定义一个日志写入器接口(LogWiter),必须遵循这个接口协议才能被日志器注册。
日志器有一个写入器的注册方法
*/
//-------日志对外的接口-------
//声明日志写入器接口
type LogWriter interface {
Write(data interface{}) error
}
//日志器(类型)
type Logger struct {
writerList []LogWriter //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{}
}
//----------文件写入器-------------
//文件写入器是众多日志写入器中的一种。
//声明文件写入器
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{}
}
//--------命令行写入器--------
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{}
}
//--------使用日志-----------
//创建日志
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 l
}
func main() {
//准备日志器
l := createLogger()
//写一个日志
l.Log("hello")
}
在log.log中还有命令行这块都输出了hello.
记一次小的语法错误:
这个错误可找死我了,最后发现,在命令行写入器那块实现接口中的方法Write写成了小写,一直不通过。
这就是犯了接口实现的条件一:接口的方法与实现接口的类型方法格式一致。
使用接口进行数据的排序
Go语言中在排序时,需要使用者通过sort.Interface接口提供数据的一些特性和操作方法。这个接口需要实现者实现的方法进行排序操作:数量(Len)、比较(Less)、交换(Swap).
接口定义的代码如下:
type Interface interface{
//获取元素数量
Len() int
//小于比较
Less(i,j int) bool
//交换元素
Swap(i,j int)
}
1.对字符串切片进行排序
package main
import (
"fmt"
"sort"
)
type Interface interface {
//获取元素数量
Len() int
//小于比较
Less(i, j int) bool
//交换元素
Swap(i, j int)
}
//将[]string定义为MyStringList类型
/*要排序的字符串切片[]string是系统定制好的类型,
无法让这个类型去实现sort.Interface排序接口。因此需要将[]string定义为自定义的类型*/
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包会通过MyStringList实现的Len()、Less()、Swap()这3个方法进行数据获取和修改*/
sort.Sort(names)
//遍历打印结果
for _, v := range names {
fmt.Printf("%s\n", v)
}
}
输出结果:
2.实现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)
}
}
输出结果:
GO1.8中对切片进行排序,直接使用Sort.Slice进行排序.不用对less ,len这些方法进行实现。
//使用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
})
使用空接口实现可以保存任意值的字典
空接口可以保存任何类型的特性,方便用于容器的设计。
示例:使用map和interface{}实现字典的功能。
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,停止遍历
//Dictionary的Visit方法需要传入类型为func(k,v interface{}) bool的回调函数。
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 { //回调函数
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
实现有限状态机(FSM)
1.状态的概念
状态机中的状态与状态间能够自由转换。每个状态可以设置它可以转移到的状态。一些状态机还允许在同一个状态间互相转换,这也需要根据实际情况进行配置。