Golang中十分nice的一個func技巧

liangxingwei發表於2018-06-23

核心:函式是一等公民

簡單的列印日誌場景

type User struct {
	Name   string
	Age    int
}

func main() {
	user := &User{Name: "Jack", Age: 18}
	log.Printf("debug level. user:%v\n",user)
}
複製程式碼

簡單單單,把使用者資訊列印一遍,問題來了,假設不同場景,需要不一樣的日誌格式,比如A,B,C場景需要json,難道在每一個場景都先json.Marshal(),然後再列印?如下

func main() {
	user := &User{Name: "Jack", Age: 18}
	s, _ := json.Marshal(user)
	log.Printf("user:%v\n",string(s))
}
複製程式碼

有一種物件導向思維的方法,User實現一個JsonString()的方法即可。但今天換一種思路,用func

一等公民func來搞點事

先寫一個函式,如下

func Debug(user *User) {
	s, _ := json.Marshal(user)
	log.Printf("debug level. user:%v\n",string(s))
}
複製程式碼

接下來的做法不是直接呼叫Debug方法,而是再定義一個函式,一個形參列表可以容納Debug方法的函式,如下

type User struct {
	Name   string
	Age    int
}

func LogUserData(user *User, fun func(u *User)) {
	fun(user)
}
func main() {
	user := &User{Name: "Jack", Age: 18}
	LogUserData(user,DebugJson)
}

func DebugJson(user *User) {
	s, _ := json.Marshal(user)
	log.Printf("debug level. user:%v\n",string(s))
}
複製程式碼

這樣做的一個好處是定義了日誌列印的框架,把具體的日誌的列印細節交由外部來實現,從而保證了對修改擴充套件的能力。其次,若是所有日誌列印需要加某些共同的前置或者後置動作,可以在LogUserData裡面加,從而避免了直接呼叫DebugJson方法,導致的在各個場景額外加前置後置條件的重複工作。此外,若是不同的動作實現細節,具有不一樣的前置或者後置動作的話,可以在動作的具體實現函式中進行增加。比如說我需要增加一個Error等級的日誌列印方法,而且Error的日誌需要簡訊來通知我係統發生error了,直接擴充套件就行了

func LogUserData(user *User, fun func(u *User)) {
    // 共同前置
    log.Printf("開始列印日誌")
	fun(user)
	// 共同後置
	log.Printf("日誌列印完成")
}
func Error(user *User) {
	// 特殊前置
	// 傳送簡訊 do Something...
	log.Printf("error level. user:%v\n",user)
}
複製程式碼

其次,fun的形參列表不一定要與user保持一致,只要有一定相關就行了,比如重新定義一個判斷未成年的函式

func IsAdult(user *User, fun func(age int) bool )bool {
	return fun(user.Age)
}
func main() {
	user := &User{Name: "Jack", Age: 18}
	b := IsAdult(user, JudgeAdult)
	fmt.Println(b)
}
func JudgeAdult(age int) bool {
	if age >= 18 {
		return true
	}
	return false
}
複製程式碼

總結

剛剛上面的做法就是 func(主體obj,動作func),又可以理解為 func(訊息obj, 訊息消費動作func),我把它叫做消費函式,這種做法在golang中得到了廣泛的採用,比如net/http

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
複製程式碼

主要的做法是用一個定好某個過程的框架的函式func,接收主體,然後接收對主體操作的action,用這個action來對主體進行某種處理。 用圖片表示就是這樣

消費函式,返回值不一定需要

比如golang net/http的HandleFunc,它只定義了收發http請求的框架,具體怎麼對請求操作,迴應什麼內容,都交由外部來定義。

其實這就是切面的思想,在一個過程,把主體和主體的處理過程切開來,處理過程交由外部實現,從而讓這個過程具有了更高的靈活度和擴充套件性。

附上所有程式碼

package main

import (
	"encoding/json"
	"log"
	"fmt"
)

type User struct {
	Name string
	Age  int
}
// 主體+動作
func LogUserData(user *User, fun func(u *User)) {
	// 共同前置
	log.Printf("開始列印日誌")
	fun(user)
	// 共同後置
	log.Printf("日誌列印完成")
}
// 主體+動作
func IsAdult(user *User, fun func(age int) bool )bool {
	return fun(user.Age)
}
func main() {
	user := &User{Name: "Jack", Age: 18}
	LogUserData(user,DebugJson)
	LogUserData(user,Info)
	LogUserData(user,Error)
	b := IsAdult(user, JudgeAdult)
	fmt.Println(b)
}
func JudgeAdult(age int) bool {
	if age >= 18 {
		return true
	}
	return false
}
func DebugJson(user *User) {
	s, _ := json.Marshal(user)
	log.Printf("debug level. user:%v\n", string(s))
}
func Info(user *User) {
	log.Printf("info level. user:%v\n", user)
}

func Error(user *User) {
	// 傳送簡訊 do Something...
	log.Printf("error level. user:%v\n", user)
}


複製程式碼

相關文章