在Golang中实现与Python装饰器类似功能的方法
Python中的闭包与装饰器
关于Python中的闭包与装饰器的知识笔者之前总结过一篇文章:Python装饰器的调用过程
实际上,装饰器是Python中的的一个语法糖,使用@装饰器装饰的函数会将被装饰的函数作为参数传入装饰器函数中,然后在装饰器函数里面做一些统一的定制化的处理。
也就是说,我们可以使用装饰器在被装饰函数执行之前或之后实现一些统一的自定制的逻辑。
比如说,笔者在实际开发中重构代码时遇到了这样的问题:新函数与旧函数的入参数跟出参一模一样,只不过由于使用的工具的版本不同需要新函数中做一下特殊的处理,为了代码简洁笔者没有在调用旧函数的地方直接将旧的函数名修改成新函数名,而是做了一个装饰器装饰上层函数,使用反射的方式将旧函数替换为新函数,下面是实现的代码:
import sys # 配置文件 老版本就不变了为True is_old_flag = False # 入参必须一样。。。 def no_print(msg): # 新逻辑 print("no_print...") def my_print(msg): # 旧逻辑 print(">>> ", msg) # 装饰器中动态修改函数 def wrapper(func): def inner(*args, **kwargs): if hasattr(sys.modules[__name__], "my_print"): if not is_old_flag: # setattr..... setattr(sys.modules[__name__], "my_print", no_print) func(*args, **kwargs) return inner @wrapper def t1(): my_print("xxxxx") t1()
当然实际中我们可以选择各种方式实现自己的需求,笔者只是抛砖引玉大概说一下装饰器的用途。
在Golang中实现Python装饰器同样的效果1 —— xorm框架中的Transaction方法与使用
Golang中也可以实现与装饰器同样的效果,简单的思路是我们可以把函数当作参数传入另外一个函数中,然后再在最外层的函数里面灵活的执行函数即可:
最近写业务代码使用golang的xorm框架,在执行事务时会使用到Transaction方法,我们可以看一下它这个方法的实现:
package xorm // Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) {
// 在f执行之前做一些处理
session := engine.NewSession() defer session.Close() if err := session.Begin(); err != nil { return nil, err } result, err := f(session) // 这里执行f if err != nil { return nil, err }
// 在f执行之后做一些处理 if err := session.Commit(); err != nil { return nil, err } return result, nil }
xorm事务方法的使用方式1
func (c *conditionalWithdrawService) handleWalletPostServiceInTransaction() (interface{}, error) { // 事务中处理 —— 接收匿名函数的方式,此时匿名函数里面需要返回下面定义方法的结果~这种方式其实相当于把“handleWalletPostService”这个方法的返回值传给了Transaction方法中的f(),然后最终Transaction方法拿到的是下面那个方法的结果 respData, err := model.DB.Walk.Transaction(func(session *xorm.Session) (interface{}, error) { return c.handleWalletPostService(session) }) if err != nil { return nil, err } return respData, nil } func (c *conditionalWithdrawService) handleWalletPostService(session *xorm.Session) (map[string]interface{}, error) { respData := make(map[string]interface{}) return respData, nil }
xorm事务方法的使用方式2 —— 这种方式较直观
type freeCoinRewardService struct { userID string configVersion int fullConfig global_config.ConfigStruct // 所有的用户配置 } func (f *freeCoinRewardService) handlePostFreeRewardInTransaction() (interface{}, error) { // 事务中处理 —— 直接接收下面定义好的方法的方式,因为在Transaction方法内部就有执行里面这个方法的地方~但是要求下面那个方法返回的是 (interface(), error) respData, err := model.DB.Walk.Transaction(f.handlePostFreeReward) if err != nil { return nil, err } return respData, nil } func (f *freeCoinRewardService) handlePostFreeReward(session *xorm.Session) (interface{}, error) { respData := make(map[string]interface{}, 0) respData["name"] = "whw" respData["age"] = 22 return respData, nil }
实际的例子
举一个实际的例子,比如说如果我们想计算一个函数的执行时间,可以这样写:
package main import ( "fmt" "time" ) // 传入的函数声明一个type type decFunc func(string) string // 计算执行时间 -- 这里的input1实际上是给被装饰函数用的 func CalTimeCost(f decFunc, input1 string) time.Duration { start := time.Now() f(input1) end := time.Now() ret := end.Sub(start) return ret } func MyTest(s1 string) string { time.Sleep(time.Second * 2) return s1 + "_666" } func main() { timeCost := CalTimeCost(MyTest, "whw") fmt.Println("函数的执行时间: ", timeCost) }
结果如下:
MyTest的执行时间为: 2.003631894
虽然代码很简单,而且将一个函数作为参数传入另外一个函数中的做法实际中不常用,但是这也算是一种技术积累把。
Golang中使用类似装饰器功能的业务代码以及使用map存放操作的函数简化if判断 *****
简化的版本看这里:
package task import ( "fmt" "testing" ) // 常量 const ( WX string = "1" OneHundred string = "2" VIP string = "3" ) // 统一处理的函数 var funcMap = map[string]func(string) (bool, error){ WX: userBindWX, OneHundred: userLevel100, VIP: userVIP, } func TestHigh(t *testing.T) { userID := "user_id_test" taskIDList := []string{"1", "2", "3", "4", "5", "6"} var retLst []UserTask // 同一个用户在不同的表中根据 userID 找到对应的记录,判断他有没有完成对应的任务 // 1、是否绑定微信 2、是否已经达到100级别 3、是否已经完成VIP充值 // 完成了上述的3个任务 就把它们的Status设置为2 // 其他任务的Status初始化为1 // TODO 使用方便的办法统一处理 for _, taskID := range taskIDList { var currStatus = 1 if currFunc, ok := funcMap[taskID]; ok { ok, err := currFunc(userID) if err != nil { panic(err) } if ok { currStatus = 2 } } currUserTask := NewUserTask(userID, taskID, currStatus) retLst = append(retLst, currUserTask) } fmt.Print("retLst: ", retLst) /* taskID为1 2 3的状态为2,剩下的都为1 retLst: [{user_id_test 1 2} {user_id_test 2 2} {user_id_test 3 2} {user_id_test 4 1} {user_id_test 5 1} {user_id_test 6 1}] */ } // 判断用户是否绑定微信 func userBindWX(userID string) (bool, error) { // TODO 业务代码省略 return true, nil } // 判断用户是否达到100级 func userLevel100(userID string) (bool, error) { // TODO 业务代码省略 return true, nil } // 判断用户是否充值了VIP func userVIP(userID string) (bool, error) { // TODO 业务代码省略 return true, nil }
这里是原版的代码:
// model包中定义的常量 // 每日任务中的成长任务 const ( BindMaster int = 8 BindWX int = 9 HasWithdraw int = 10 HasInvite int = 11 ) // 任务是否完成的状态 const ( NotFinish int = 1 // 未完成 FinishButNotReward int = 2 // 已完成未领取 Rewarded int = 3 // 已领取 )
~~~
var funcMap = map[int]func(string) (bool, error){ model.BindMaster: userInvitee, model.BindWX: userBindWX, model.HasWithdraw: userWithdraw, model.HasInvite: userInvitor, } // 根据userValue找到user_task的配置然后在Redis中创建 func GenUserTaskDataInRedisByUserIdAndUserValue(userId, userValue string) ([]UserTaskModel, error) { userTaskModelList := make([]UserTaskModel, 0) tasks := GetTaskList(userValue) for _, taskItem := range tasks { userTask := NewUserTask(&taskItem, userId) // 适配"成长任务":8: 绑定师父 9: 绑定微信 10: 完成第一笔提现 11: 邀请好友
// 下面的4个函数的模式一模一样,完全可以统一处理,这样省去了4个if判断~ if currFunc, ok := funcMap[userTask.TaskId]; ok { status, err := getTaskStatusDecorator(currFunc, userId, taskItem.TaskId) if err != nil { return nil, err } userTask.Status = status } userTaskModelList = append(userTaskModelList, *userTask) } // HMSet (~~ 业务代码 可忽略) err := BatchCreateUserTaskForSingleUserInRedis(userTaskModelList) if err != nil { return nil, err } return userTaskModelList, nil } // 判断用户是否绑定师傅 实际上是找这个用户是否被邀请过 func userInvitee(userID string) (bool, error) { _, has, err := invite.GetByInvitee(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } // 被邀请过证明完成过任务了 if has { return true, nil } return false, nil } // 判断用户是否绑定微信 func userBindWX(userID string) (bool, error) { userModel, has, err := users.GetUserByUserId(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } // 如果有这个用户 判断wx_id是否为空 if has { if userModel.WxId != "" { return true, nil } } return false, nil } // 判断用户是否提过现 func userWithdraw(userID string) (bool, error) { withMoneyRecord := WithdrawRecordModel{UserId: userID} exist, err := withMoneyRecord.Exist() if err != nil { return false, errnum.New(errnum.DbError, err) } if exist { return true, nil } return false, nil } // 判断用户之前是否邀请过人 func userInvitor(userID string) (bool, error) { uLst, err := invite.QueryByInviter(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } if len(*uLst) > 0 { return true, nil } return false, nil } // 统一处理判断状态 3个参数:判断是否完成过任务的函数judgeFunc、userID、taskID func getTaskStatusDecorator(judgeFunc func(userID string) (bool, error), userID string, taskID int) (int, error) { // 从user_game_task表中找记录 userGameTask, has, err := GetUserGameTaskByUserIdTaskIdNoSession(userID, taskID) if err != nil { return 0, errnum.New(errnum.DbError, err) } // 如果有记录 就用这里的 if has { return userGameTask.Status, nil } // 如果没有记录需要在user_game_task中创建一条记录 status需要根据实际情况来判断 userGameTaskModel := UserGameTaskModel{ UserGameTaskId: uuid.RandStringRunesTime(4), UserId: userID, GameTaskId: taskID, } // 判断用户实际上有没有完成对应传过来判断的函数中的任务 completeTask, err := judgeFunc(userID) if err != nil { return 0, err } if completeTask { // 完成了任务还需要判断一下有没有领奖 typeCoinHistory := TypeCoinHistoryModel{UserID: userID, ItemID: taskID} exist, err := typeCoinHistory.Exist() if err != nil { return 0, errnum.New(errnum.DbError, err) } if exist { userGameTaskModel.Status = model.Rewarded } else { userGameTaskModel.Status = model.FinishButNotReward } } else { userGameTaskModel.Status = model.NotFinish } // 创建user_game_task记录 err = userGameTaskModel.CreateNoSession() if err != nil { return 0, errnum.New(errnum.DbError, err) } return userGameTaskModel.Status, nil }
~~~