本文章会持续更新更多golang常用小技巧,以各种小技巧开拓思路;
0. 近期更新 (2021年06月03日)
- 链式调用
- ErrorResult 多值封装
- JsonResult 分装
1. 单例模式
- 多用于只实例化一次,如配置模块,日志模块等;
package main
import (
"fmt"
"sync"
)
var cc *WebConfig
var once sync.Once
type WebConfig struct {
Port int `json:"port"`
}
func GetConfig() *WebConfig {
once.Do(func() {
cc = &WebConfig{Port: 8080}
})
return cc
}
func main() {
cfg1 := GetConfig()
cfg2 := GetConfig()
fmt.Println(cfg1 == cfg2)
}
2. 函数执行超时控制
package main
import (
"fmt"
"time"
)
//1 .业务过程放到协程
// 2、把业务结果塞入channel
func job() chan string{
ret:=make(chan string)
go func() {
time.Sleep(time.Second*2)
ret<- "success"
}()
return ret
}
func run() (interface{}, error) {
c:=job()
select {
case r:=<-c:
return r,nil
case <-time.After(time.Second*3):
return nil,fmt.Errorf("time out")
}
}
func main() {
fmt.Println(run())
}
3. 反射使用示例
- 主要理清楚
reflect.Kind和reflect.Value的用法
package main
import (
"fmt"
"reflect"
)
type User struct {
UserId int `name:"uid" bcd:"3456"`
UserName string
}
func Map2Struct(m map[string]interface{},u interface{}) {
v:=reflect.ValueOf(u)
if v.Kind()==reflect.Ptr{
v=v.Elem()
if v.Kind()!=reflect.Struct{
panic("must struct")
}
findFromMap:= func(key string,nameTag string ) interface {}{
for k,v:=range m{
if k==key || k==nameTag {
return v
}
}
return nil
}
for i:=0;i<v.NumField();i++{
get_value:=findFromMap(v.Type().Field(i).Name,v.Type().Field(i).Tag.Get("name"))
if get_value!=nil && reflect.ValueOf(get_value).Kind()==v.Field(i).Kind(){
v.Field(i).Set(reflect.ValueOf(get_value))
}
}
}else{
panic("must ptr")
}
}
func main() {
u:=&User{}
m:=map[string]interface{}{
"id":123,
"uid":101,
"UserName":"shenyi",
"age":19,
}
Map2Struct(m,u)
fmt.Printf("%+v", u)
}
4. 构造函数技巧
- 作用是提高代码可维护性和阅读性,但是这种写法相对的会损失一些性能
package main
import (
"fmt"
)
type UserAttrFunc func(*User) //设置User属性的 函数类型
type UserAttrFuncs []UserAttrFunc
func(this UserAttrFuncs) apply(u *User) {
for _,f:=range this{
f(u)
}
}
func WithUserID(id int) UserAttrFunc {
return func(u *User) {
u.Id=id
}
}
func WithUserName(name string) UserAttrFunc {
return func(u *User) {
u.Name=name
}
}
type User struct {
Id int
Name string
Sex byte
}
// 有选择性的对ID 进行赋值
func NewUser(fs ...UserAttrFunc) *User {
u:= new(User)
UserAttrFuncs(fs).apply(u)
return u
}
func main() {
u:=NewUser(
WithUserName("shenyi"),
WithUserID(105),
)
fmt.Println(u)
}
5. 简单工厂模式
package main
import "fmt"
type UserType int
type User interface {
GetRole() string
}
type Member struct {}
func(this *Member) GetRole() string {
return "会员用户"
}
type Admin struct {}
func(this *Admin) GetRole() string {
return "后台管理用户"
}
// 枚举类型
const (
Mem UserType = iota
Adm
)
func CreateUser(t UserType) User{
switch t {
case Mem:
return new(Member)
case Adm:
return new(Admin)
default:
return new(Member)
}
}
func main() {
fmt.Println(CreateUser(Adm).GetRole())
}
6. 抽象工厂模式
- 抽象工厂模式,主要也是在多态情况下的一种高维护性和扩展性的设计模式,符合高层代码不应依赖具体模块的实现而应依赖于抽象这么一个设计思想;
package main
import "fmt"
// 实体
type User interface {
GetRole() string
}
type Member struct {}
func(this *Member) GetRole() string {
return "会员用户"
}
type Admin struct {}
func(this *Admin) GetRole() string {
return "后台管理用户"
}
const (
Mem=iota
Adm
)
// 抽象
type AbstractFactory interface {
CreateUser() User
}
type MemberFactory struct {}
func(this *MemberFactory) CreateUser() User{
return &Member{}
}
type AdminFactory struct {}
func(this *AdminFactory) CreateUser() User{
return &Admin{}
}
func main() {
var fact AbstractFactory=new(AdminFactory)
fmt.Println(fact.CreateUser().GetRole())
}
7. 装饰器模式
- 装饰器是体现AOP思想的一种设计模式;在无需改动原有方法的前提下添加新功能;
package main
import "net/http"
func CheckLogin(f http.HandlerFunc) http.HandlerFunc{
return func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Query().Get("token")==""{
writer.Write([]byte("token error"))
}else{
f(writer,request)
}
}
}
func index(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("index"))
}
func main() {
http.HandleFunc("/",CheckLogin(index) )
http.ListenAndServe(":8080",nil)
}
8. 简易前缀树
- 多用于在路由框架,进行路由匹配
package main
import "fmt"
type Node struct {
isend bool //是否最后一个
Children map[string]*Node //这一步 用map就省的遍历了
}
func NewNode() *Node {
return &Node{Children:make(map[string]*Node)}
}
type Trie struct {
root *Node
}
func NewTrie() *Trie {
return &Trie{root:NewNode()}
}
func(this *Trie) Insert(str string ){
// 第一个肯定是空节点
current:=this.root
for _,item:=range ([]rune)(str){
if _,ok:=current.Children[string(item)];!ok{
current.Children[string(item)]=NewNode()
}
current=current.Children[string(item)]
}
current.isend=true //最后一个
}
func(this *Trie) Search(str string ) bool{
current:=this.root
for _,item:=range ([]rune)(str){
if _,ok:=current.Children[string(item)];!ok{
return false
}
current=current.Children[string(item)]
}
return current.isend //最后一个
}
func test() {
strs:=[]string{"go","gin","golang","goapp","guest"}
tree:=NewTrie()
for _,s:=range strs{
tree.Insert(s)
}
// 最终一个都匹配不到
strs=append(strs,"gi","gogo","gia")
for _,s:=range strs{
fmt.Println(tree.Search(s))
}
}
func main() {
test()
}
9. 行读取文件或字符串
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"strings"
)
func dropCR(data []byte) []byte {
if len(data) > 0 && data[len(data)-1] == ':' {
return data[0 : len(data)-1]
}
return data
}
func split(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, ':'); i >= 0 {
// We have a full newline-terminated line.
return i + 1, dropCR(data[0:i]), nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), dropCR(data), nil
}
// Request more data.
return 0, nil, nil
}
func main() {
/*
Scanner 方法
NewScanner 创建 Scanner
Scanner.Split 设置处理函数
Scanner.Scan 获取当前token, 扫描下一token
Scanner.Bytes 将token以 []byte 的形式返回
Scanner.Text 将token以 string 的形式返回
Scanner.Err 获取处理方法返回的错误
*/
reader := strings.NewReader("aaa:bbb:ccc:ddd:eee:fff:ggg")
scanner := bufio.NewScanner(reader)
/*
ScanBytes 将token处理为单一字节
ScanRunes 将token处理为utf-8编码的unicode码
ScanWords 以空格分隔
tokenScanLines 以换行符分分割token
*/
// 替换原有默认以'\n'的形式切割,上面两端代码是模仿源码的分割函数
scanner.Split(split)
count := 0
for scanner.Scan() {
log.Println(scanner.Text())
count++
}
fmt.Println("一共有", count, "行")
}
10. 管道模式
- 管道模式有点像流式处理,类似linux里面的
echo 'hello world' |grep 'world';这也是go开发中很常见的技巧
package main
import (
"fmt"
"sync"
"time"
)
type Cmd func(list []int) chan int
type PipeCmd func(in chan int) chan int //支持管道的函数
// 模拟从数据库获取数据
func GetData(list []int) chan int {
c := make(chan int)
go func() {
defer close(c)
for _, num := range list {
// 模拟从数据库从获取数据的延迟
time.Sleep(time.Millisecond*500)
c <- num
}
}()
return c
}
// 数据处理
func Multiply10(in chan int) chan int { //这个函数是支持管道的
out := make(chan int)
go func() {
defer close(out)
for num := range in {
// 1秒延迟
time.Sleep(time.Second * 1)
out <- num * 10
}
}()
return out
}
// 管道函数
func Pipe(args []int, c1 Cmd, cs ...PipeCmd) chan int {
ret := c1(args)
if len(cs) == 0 {
return ret
}
retList := make([]chan int, 0)
for index, c := range cs {
if index == 0 {
retList = append(retList, c(ret))
} else {
// 获取最后一个就是最新的结果
getChan := retList[len(retList)-1]
retList = append(retList, c(getChan))
}
}
return retList[len(retList)-1]
}
func Test(testData []int) {
// 通过GetData把数据塞入管道,然后处理完一个往后传递类似流水线;
ret := Pipe(testData, GetData, Multiply10, Multiply10)
for r := range ret {
fmt.Printf("%d ", r)
}
}
func main() {
Test([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
}
11. 管道模式之多路复用
-
管道的多路复用是指在同一个管道的数据,同时被不同的函数实体进行并发处理,从而有效提高处理效率;
ps: 注意一下管道需并发运行注意关闭;
package main
import (
"fmt"
"sync"
"time"
)
type Cmd func(list []int) chan int
type PipeCmd func(in chan int) chan int //支持管道的函数
// 模拟从数据库获取数据
func GetData(list []int) chan int {
c := make(chan int)
go func() {
defer close(c)
for _, num := range list {
// 模拟从数据库从获取数据的延迟
time.Sleep(time.Millisecond*500)
c <- num
}
}()
return c
}
// 数据处理
func Multiply10(in chan int) chan int { //这个函数是支持管道的
out := make(chan int)
go func() {
defer close(out)
for num := range in {
// 1秒延迟
time.Sleep(time.Second * 1)
out <- num * 10
}
}()
return out
}
// 管道多路复用
func PipeM(args []int, c1 Cmd, cs ...PipeCmd) chan int {
ret := c1(args) //找偶数
out := make(chan int)
wg := sync.WaitGroup{}
for _, c := range cs {
getChan := c(ret)
wg.Add(1)
go func(input chan int) {
defer wg.Done()
for v := range input {
// 模拟延迟
time.Sleep(1*time.Second)
out <- v
}
}(getChan)
}
go func() {
// 这里值得注意的是管道知识的运用,需要关闭channel
// 如果未关闭管道,当所有输入的协程执行完毕后,主线程的读取就会死锁;
defer close(out)
wg.Wait()
}()
return out
}
func Test(testData []int) {
// 全程无阻塞数据获取后直接返回管道
ret := PipeM(testData, GetData, Multiply10, Multiply10)
for r := range ret {
fmt.Printf("%d ", r)
}
}
func main() {
Test([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
}
- 一个封装好的通用管道模式多路复用
package pipe
import "sync"
type InChan chan interface{}
type OutChan chan interface{}
type CmdFunc func(args ...interface{}) InChan
type PipeCmdFunc func(in InChan) OutChan
type Pipe struct{
Cmd CmdFunc
PipeCmd PipeCmdFunc
Count int
}
func NewPipe() *Pipe {
return &Pipe{Count:1}
}
// 设置获取数据的函数
func(this *Pipe) SetCmd(c CmdFunc) {
this.Cmd=c
}
// 设置管道并发命令
func(this *Pipe) SetPipeCmd(c PipeCmdFunc,count int ) {
this.PipeCmd=c
this.Count=count
}
func(this *Pipe) Exec(args ...interface{}) OutChan {
in:=this.Cmd(args)
out:=make(OutChan)
wg:=sync.WaitGroup{}
for i:=0;i<this.Count;i++{
getChan:=this.PipeCmd(in)
wg.Add(1)
go func(input OutChan) {
defer wg.Done()
for v:=range input{
out<-v
}
}(getChan)
}
go func() {
defer close(out)
wg.Wait()
}()
return out
}
12. 链式调用
package main
import (
"fmt"
)
type UserAttrFunc func(*User) //设置User属性的 函数类型
type UserAttrFuncs []UserAttrFunc
func(this UserAttrFuncs) apply(u *User) {
for _,f:=range this{
f(u)
}
}
func WithUserID(id int) UserAttrFunc {
return func(u *User) {
u.Id=id
}
}
func WithUserName(name string) UserAttrFunc {
return func(u *User) {
u.Name=name
}
}
type User struct {
Id int
Name string
}
func NewUser(fs ...UserAttrFunc) *User {
u:= new(User)
//强制转换成UserAttrFuncs
UserAttrFuncs(fs).apply(u)
return u
}
// 通过返回自身进行链式调用
func (u *User) Mutate(fs ...UserAttrFunc) *User {
UserAttrFuncs(fs).apply(u)
return u
}
func main() {
u:=NewUser(
WithUserName("lisi"),
WithUserID(105),
)
fmt.Println(u)
// 链式调用
u.Mutate(WithUserID(1)).Mutate(WithUserName("zhangsan"))
fmt.Println(u)
}
13. ErrReuslt 多值返回封装
- 更好的统一处理错误,可以以这个原型继续扩展;
package main
import (
"encoding/json"
"fmt"
"sync"
)
type ErrorResult struct {
data interface{}
err error
}
func (this *ErrorResult) Unwrap() interface{} {
if (this.err != nil) {
panic(this.err.Error())
}
return this.data
}
// 按着一定格式进行返回如: data,err
func Result(values ...interface{}) *ErrorResult {
if len(values) == 1 {
if values[0] == nil {
return &ErrorResult{nil, nil}
}
if err, ok := values[0].(error); ok {
return &ErrorResult{nil, err}
}
}
if len(values) == 2 {
if values[1] == nil {
return &ErrorResult{values[0], nil}
}
if err, ok := values[1].(error); ok {
return &ErrorResult{values[0], err}
}
}
err := fmt.Errorf("Result format error, valuses must be (err) or (data, err)")
return &ErrorResult{nil, err}
}
func getErrorResult() (int, error) {
return 100, fmt.Errorf("It is a error.")
}
func getResult() (int, error) {
return 100, nil
}
func coreRun() (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()
// 这里会panic err 中断执行
Result(getErrorResult()).Unwrap()
fmt.Println("exit")
return
}
func main() {
err := coreRun()
fmt.Println(err)
}
14. JsonResult 分装
- JsonResult 我们在web的返回值或者命令行返回值经常需要统一的的返回;考察的是go装饰器的使用;
- 以下代码和第13段代码的结合使用,且使用了sync.Pool;
package main
import (
"encoding/json"
"fmt"
"sync"
)
type ErrorResult struct {
data interface{}
err error
}
func (this *ErrorResult) Unwrap() interface{} {
if (this.err != nil) {
panic(this.err.Error())
}
return this.data
}
// 按着一定格式进行返回如: data,err
func Result(values ...interface{}) *ErrorResult {
if len(values) == 1 {
if values[0] == nil {
return &ErrorResult{nil, nil}
}
if err, ok := values[0].(error); ok {
return &ErrorResult{nil, err}
}
}
if len(values) == 2 {
if values[1] == nil {
return &ErrorResult{values[0], nil}
}
if err, ok := values[1].(error); ok {
return &ErrorResult{values[0], err}
}
}
err := fmt.Errorf("Result format error, valuses must be (err) or (data, err)")
return &ErrorResult{nil, err}
}
type JsonResult struct {
Message string `json:"message"`
Code string `json:"code"`
Result interface{} `json:"result"`
}
func NewJsonResult(message string, code string, result interface{}) *JsonResult {
return &JsonResult{Message: message, Code: code, Result: result}
}
// sync.Pool 用于复用已经使用过的对象,节省频繁创建和回收资源的时间
var ResultPool *sync.Pool
func init() {
ResultPool = &sync.Pool{
New: func() interface{} {
return NewJsonResult("", "", "")
},
}
}
// JP function 只是为了展示更好的解耦一些状态的输出
type JP func(code int, v interface{})
func jsonDumps(code int, v interface{}) {
jb, _ := json.Marshal(v)
fmt.Println(code, string(jb))
}
type Output func(j JP, v interface{})
type ResultFunc func(message string, code string, result interface{}) func(output Output)
func R(j JP) ResultFunc {
return func(message string, code string, result interface{}) func(output Output) {
r := ResultPool.Get().(*JsonResult)
// 记住放回去
defer ResultPool.Put(r)
r.Message = message
r.Code = code
r.Result = result
return func(output Output) {
output(j, r)
}
}
}
// 如成功,返回200
func StateOk(j JP, v interface{}) {
j(200, v)
}
// 如失败,返回500
func StateError(j JP, v interface{}) {
j(500, v)
}
func getErrorResult() (int, error) {
return 100, fmt.Errorf("It is a error.")
}
func getResult() (int, error) {
return 100, nil
}
func main() {
R(jsonDumps)("hello world", "1001", Result(getResult()).Unwrap())(StateOk)
R(jsonDumps)("hello world", "1001", Result(getResult()).Unwrap())(StateError)
}