目录结构设计
golang强制要求不能有循环依赖,同时同一个目录下(不包括子目录)只能有一个包名,所以项目结构的设计、代码依赖关系的设计就非常重要了,如果前期设计项目结构和代码结构的时候没有处理好,等后面项目越来越大,业务逻辑越来越复杂的时候可能就会出现问题,而且改起来代价可能会很大。
interface和struct
golang的struct实现接口时不需要显示声明,只要struct实现了想实现的接口的所有方法就行
struct没有真正意义上的继承关系,继承是通过类似组合的方式实现的
声明继承struct:
type BusinessEntity struct {
BasicEntity `json:"-"`
BusinessId string `json:"business_id"`
BusinessField int64 `json:"business_field"`
OtherField int64 `json:"other_field"`
}
由于没有真正意义上的继承,当一个函数声明的入参类型是父类型结构体时,是无法将子类型结构体的实例传进去的
无法将子struct传给父struct类型的参数:
func Check(entity BasicEntity) (err error) {
err = tools.Check(entity, tools.Option{NoCover: true, IgnoreEmpty: true})
return
}
func main(){
wti := WorkTicketInfo{BasicEntity:BasicEntity{}}
err := Check(wti) // 编译报错,类型不匹配
}
只能将WorkTicketInfo实例包含的BasicEntity实例取出来作为入参
取出父struct实例作为入参:
func main(){
wti := WorkTicketInfo{}
err := Check(wti.BasicEntity) // 编译通过
}
从上面这个例子可以看出golang的继承并不是真正的继承
从这种设计也可以看出语言设计者希望使用者设计和实现功能时更多的从面向接口的方向考虑
协程
循环创建协程时不能通过闭包的方式传递参数,因为协程是异步的,当协程开始执行时循环已经完成,参数已经遍历完成,这时去拿只能拿到最后一个参数
错误传递参数方式:
for _, holder := range asyncTaskList {
go func() {
defer func() {
if err := recover(); err != nil {
// TODO 日志
}
}()
if !tools.ShouldShortCircuit(holder.Tag()) {
holder.Run() // 所有协程都只能拿到最后一个holder
}
}()
}
必须要通过传参的方式传入函数才能拿到正确的数据
正确传递参数方式:
for _, holder := range asyncTaskList {
go func(taskHolder TaskHolder) {
defer func() {
if err := recover(); err != nil {
// TODO 日志
}
}()
if !tools.ShouldShortCircuit(taskHolder.Tag()) {
taskHolder.Run()
}
}(holder)
}
使用细节
异或
golang没有提供异或表达式,需要自己实现
func Xor(X, Y bool) bool {
return (X || Y) && !(X && Y)
}
异常处理
异常处理时正常的注册方式是defer执行一个匿名函数
正常的异常处理:
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
不能在处理panic时再调用其它函数来处理,否则无法正确处理异常
失败的异常处理:
var panicHandler = PanicHandler{panicHandler: func() {
if err := recover(); err != nil { // 获取不到error
fmt.Println(err)
}
}}
defer func() {
panicHandler.panicHandler()
}()
上面这种实现方式在panic时进入defer函数后再次调用其它函数处理异常,会导致panic后调用栈变化,recover()函数无法正确获取到当前栈帧的panic信息而无法处理异常
类型断言
go可以通过switch value.(type)的方式进行断言
语法与其它语言的switch case一致
switch类型判断示例:
switch value.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
formattedValues = append(formattedValues, fmt.Sprintf("%v", value))
default:
formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
}
go的switch语法默认每一个case都会break,如果想接着执行下一个case中的代码,则需要在case的最后添加fallthrough关键字
但是fallthrough关键字不能在类型判断的switch代码块中使用,所以如果你需要对某些类型做一些预处理后再与其它类型一起处理,就只有两种方案:写两个switch或者写两遍处理逻辑
类型转换
数字类型转换
在golang中,把一个不知道具体数字类型的参数转换成指定数字类型是一件非常麻烦的事
因为go中没有通用的表示数字类型的类型,如果不自定义类型的话就只能用interface{}类型的参数来接收值,而如果想将值转换成特定数字类型必须先知道待转换的值的原始类型。所以为了实现这个小功能必须实现类似下面这样的代码
func ConvertToInt64(data interface{}) int64 {
switch data.(type) {
case int:
return int64(data.(int))
case int8:
return int64(data.(int8))
case int16:
return int64(data.(int16))
case int32:
return int64(data.(int32))
case int64:
return data.(int64)
case uint:
return int64(data.(uint))
case uint8:
return int64(data.(uint8))
case uint16:
return int64(data.(uint16))
case uint32:
return int64(data.(uint32))
case uint64:
return int64(data.(uint64))
case float32:
return int64(data.(float32))
case float64:
return int64(data.(float64))
case complex64:
return int64(real(data.(complex64)))
case complex128:
return int64(real(data.(complex128)))
case json.Number:
i, err := data.(json.Number).Int64()
if err != nil {
panic(err)
}
return i
case string:
i, err := strconv.ParseInt(data.(string), 10, 64)
if err != nil {
panic(err)
}
return i
}
panic(response.DataError.Handle(fmt.Sprintf("ConvertToInt64:data %v must be number or string,not %s", data, reflect.TypeOf(data).Name())))
}
需要针对每一种类型做断言然后再转换(go有14种数字类型)
可变参数
go中的可变参数通过…来声明,与python中的*args是不一样的
python中可以这么写
python可变参数 折叠源码
def a_function(param1,*args):
pass
a_function(value1, value2, *values)
但是go中不可以这么写,参数必须对齐
func aFunction(param1 interface{}, params ...interface{}){
return
}
func main(){
var value1, value2 interface{}
var values []interface{}
aFunction(value1, value2, values...) // 报错,value2参数类型错误, values参数未使用
}
option设计模式
golang不支持可变参数、默认参数,也不支持重载。正常情况下需要传递可选参数,只能通过传默认值或者实现一组不同名称不同参数列表的函数的方式。
option设计模式可以用来解决在这些限制条件下传递可选参数的问题的。
这种设计模式的主要思路是将设置可选参数的函数或者实例传递进来,然后再内部调用这些函数来设置或者获得可选参数。由于这些函数可以设计为拥有相同出入参列表的函数,可以同一为相同类型。再利用golang支持的可变入参就可以达到实现传递可选入参的目的。
option设计模式示例:
// 定义设置可选参数函数的类型,可省略步骤
type TaskOption func(TaskHolder)
// 定义具体的设置可选参数函数
func Delay(delay int64) TaskOption {
// 返回的函数是TaskOption类型的,通过闭包将可选参数传递进去
// holder参数是用来承载可选参数的结构体
return func(holder TaskHolder) {
holder.setDelay(delay)
}
}
// 使用option参数的函数
func RegisterTask(tag string, task AsyncTask, options ...TaskOption) {
holder := taskHolder{
task: task,
tag: tag,
}
// 通过option函数将可选参数设置进holder中
for _, o := range options {
o(&holder)
}
// 使用参数
asyncTaskList.Append(&holder)
}