乐趣区

关于后端:Go-中几种常见的编程模式

模式能够了解成最佳实际,或者是约定俗成的标准或套路,相熟一些常见模式能够不便了解我的项目代码。本文是参考左耳朵耗子的专栏文章做的笔记,另外也缅怀一下耗子叔。

slice 切片的数据共享与扩容迁徙

切片的数据共享

切片就像是在数组上开的窗口,透过切片窗口能够看到和批改底层数组。

这段代码中,foo 和 bar 都是来源于一个底层数组的切片,在 foo 或 bar 任意一个切片做批改,都会影响到另一个切片,或者说在另一个切片也能看到其余切片做出的批改。

foo = make([]int, 5)
foo[3] = 42
foo[4] = 100

bar  := foo[1:4]
bar[1] = 99

左图是未修改时的示例,右图是批改后的示例。

append 笼罩后续切片景象

给一个切片 append 元素时,如果 append 后超出原有切片的容量则会产生扩容,确切来说应该是重分配内存把原有元素迁徙到新地址,而如果 append 后没超过原有容量则不产生扩容迁徙。

上面代码是一个原空间有余,而产生扩容迁徙例子

a := make([]int, 32)
b := a[1:16]
a = append(a, 1)
a[2] = 42

往 a 中 append 元素,a 本来曾经是满格 32 容量了,append 之后产生扩容迁徙,与 b 不再共享数据,之后在 a 上批改反映不到 b 上。

上面代码示意的是 append 后容量足够,在原位批改且因为数据共享导致笼罩掉了前面的切片

func main() {path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')

    dir1 := path[:sepIndex]
    dir2 := path[sepIndex+1:]

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB
}

要防止这个问题产生能够在生成切片 dir1 时,指定其容量就等于初始化长度,使得一旦后续再往里 append 元素必然导致扩容迁徙。dir1 := path[:sepIndex:sepIndex]

应用 reflect.DeepEqual() 深度比拟

深度比拟能够深刻简单类型外部一一属性比拟

import (
    "fmt"
    "reflect"
)

func main() {v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2))
    //prints: v1 == v2: true

    m1 := map[string]string{"one": "a","two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2))
    //prints: m1 == m2: true

    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2))
    //prints: s1 == s2: true
}

Functional Options 高阶函数实现可选配置

初始化某实例时,有多种可选的初始化参数,能够应用独自 Config 对象,也能够应用 Builder 模式。这里介绍一种通过函数式编程来初始化的做法。

Server 就是要实例化的对象


type Server struct {
    Addr     string
    Port     int
    Protocol string
    Timeout  time.Duration
    MaxConns int
    TLS      *tls.Config
}

首先定义一种函数类型,接管 Server 的指针,目标是批改 Server 外部的属性

type Option func(*Server)

定义一组返回 Option 的办法,接管定制参数,将参数写入到 Option 中,实现高阶函数的成果。


// 返回一个 Option,Option 外部来批改 Server
func Protocol(p string) Option {return func(s *Server) {s.Protocol = p}
}
func Timeout(timeout time.Duration) Option {return func(s *Server) {s.Timeout = timeout}
}
func MaxConns(maxconns int) Option {return func(s *Server) {s.MaxConns = maxconns}
}
func TLS(tls *tls.Config) Option {return func(s *Server) {s.TLS = tls}
}

最初定义初始化函数,函数除了必选参数外,其余可选参数通过变长参数传入,通过 for 循环启动每个 option 办法设置 server 实例,实现可选参数初始化。

func NewServer(addr string, port int, options ...Option) (*Server, error) {
    srv := Server{
        Addr:     addr,
        Port:     port,
        Protocol: "tcp",
        Timeout:  30 * time.Second,
        MaxConns: 1000,
        TLS:      nil,
    }
    for _, option := range options {option(&srv)
    }
    //...
    return &srv, nil
}

func FunctionOptions() {s1, _ := NewServer("localhost", 1024)
    s2, _ := NewServer("localhost", 2048, Protocol("udp"), TLS(&tls.Config{}))
    s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))

    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
}
&{localhost 1024 tcp 30s 1000 <nil>}
&{localhost 2048 udp 30s 1000 0x1400011a000}
&{0.0.0.0 8080 tcp 5m0s 1000 <nil>}

实际上就是利用了高阶函数来初始化变量,间接实现保留状态的成果。

通过嵌入组合的形式实现管制反转 IoC

在业务逻辑代码中耦合进管制逻辑,会导致在编写业务逻辑时须要解决业务之外的事,而且管制逻辑耦合进业务中,只能实用于以后业务逻辑,无奈被复用。

管制反转是一种解耦思维,将本来耦合在业务逻辑中的管制逻辑独自拆出来实现,不再让业务逻辑在解决业务的同时还要去实现管制逻辑,而是专一解决业务。

假如当初有一个 IntSet 构造,用来解决整型数据:

type IntSet struct {data map[int]bool
}

func NewIntSet() IntSet {return IntSet{make(map[int]bool)}
}
func (set *IntSet) Add(x int) {set.data[x] = true
}
func (set *IntSet) Delete(x int) {delete(set.data, x)
}
func (set *IntSet) Contains(x int) bool {return set.data[x]
}

当初要为其减少 undo 性能

一种做法是在 IntSet 根底上残缺实现一遍 undo 性能,即 IntSet 本人保护一个 do functions 切片,本人保护入栈出栈操作。

type UndoableIntSet struct { // Poor style
    IntSet    // Embedding (delegation)
    functions []func()
}

func NewUndoableIntSet() UndoableIntSet {return UndoableIntSet{NewIntSet(), nil}
}

func (set *UndoableIntSet) Add(x int) { // Override
    if !set.Contains(x) {set.data[x] = true
        set.functions = append(set.functions, func() {set.Delete(x) })
    } else {set.functions = append(set.functions, nil)
    }
}

func (set *UndoableIntSet) Delete(x int) { // Override
    if set.Contains(x) {delete(set.data, x)
        set.functions = append(set.functions, func() {set.Add(x) })
    } else {set.functions = append(set.functions, nil)
    }
}

func (set *UndoableIntSet) Undo() error {if len(set.functions) == 0 {return errors.New("No functions to undo")
    }
    index := len(set.functions) - 1
    if function := set.functions[index]; function != nil {function()
        set.functions[index] = nil // For garbage collection
    }
    set.functions = set.functions[:index]
    return nil
}

下面代码这就是所谓的管制逻辑依赖业务逻辑,或者说管制逻辑被耦合进了业务逻辑之中。undo 是管制逻辑,IntSet 是业务逻辑,IntSet 在解决本来整数数据相干的事,当初却要关怀函数调用秩序的事,而 undo 管制逻辑嵌入到了 IntSet 中,只能服务于 IntSet,而 undo 本来是一个通用逻辑,可能别的数据结构也会用到 undo 性能。

是否将管制逻辑抽出来,独自实现,而后大家共用一套协定,面对协定开发就好了?

现申明一个 Undo 类型并给 Undo 类型申明几个办法,专门解决 undo 操作,它实际上就是一组 function 的切片


type Undo []func()

func (undo *Undo) Add(function func()) {*undo = append(*undo, function)
}

func (undo *Undo) Undo() error {
    functions := *undo
    if len(functions) == 0 {return errors.New("No functions to undo")
    }
    index := len(functions) - 1
    if function := functions[index]; function != nil {function()
        functions[index] = nil // For garbage collection
    }
    *undo = functions[:index]
    return nil
}

IntSet 通过嵌入一个 Undo 构造,间接实现 undo 性能,这样管制逻辑封装在 Undo 中,业务逻辑 IntSet 只关怀解决整型数据就好了。


type IntSet struct {data map[int]bool
    undo Undo
}

func NewIntSet() IntSet {return IntSet{data: make(map[int]bool)}
}

func (set *IntSet) Undo() error {return set.undo.Undo()
}

func (set *IntSet) Contains(x int) bool {return set.data[x]
}

func (set *IntSet) Add(x int) {if !set.Contains(x) {set.data[x] = true
        set.undo.Add(func() {set.Delete(x) })
    } else {set.undo.Add(nil)
    }
}

func (set *IntSet) Delete(x int) {if set.Contains(x) {delete(set.data, x)
        set.undo.Add(func() {set.Add(x) })
    } else {set.undo.Add(nil)
    }
}

实质还是面向接口编程和组合优于继承的思维,一方面是制订交互协定,另一方面是依据本身需要抉择适合组件组合到一起。

通过反射实现泛型版的 Map Reduce Filter

泛型函数就是接管任意类型的 slice,解决后返回相应类型的后果。

最间接的不做类型判断的泛型 Map

func Map(data interface{}, fn interface{}) []interface{} {vfn := reflect.ValueOf(fn)
    vdata := reflect.ValueOf(data)
    result := make([]interface{}, vdata.Len())

    for i := 0; i < vdata.Len(); i++ {result[i] = vfn.Call([]reflect.Value{vdata.Index(i)})[0].Interface()}
    return result
}

退出类型判断的 Map

func Transform(slice, function interface{}) interface{} {return transform(slice, function, false)
}

func TransformInPlace(slice, function interface{}) interface{} {return transform(slice, function, true)
}

func transform(slice, function interface{}, inPlace bool) interface{} {
 
  //check the `slice` type is Slice
  sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {panic("transform: not slice")
  }

  //check the function signature
  fn := reflect.ValueOf(function)
  elemType := sliceInType.Type().Elem()
  if !verifyFuncSignature(fn, elemType, nil) {panic("trasform: function must be of type func(" + sliceInType.Type().Elem().String() + ") outputElemType")
  }

  sliceOutType := sliceInType
  if !inPlace {sliceOutType = reflect.MakeSlice(reflect.SliceOf(fn.Type().Out(0)), sliceInType.Len(), sliceInType.Len())
  }
  for i := 0; i < sliceInType.Len(); i++ {sliceOutType.Index(i).Set(fn.Call([]reflect.Value{sliceInType.Index(i)})[0])
  }
  return sliceOutType.Interface()}

func verifyFuncSignature(fn reflect.Value, types ...reflect.Type) bool {

  //Check it is a funciton
  if fn.Kind() != reflect.Func {return false}
  // NumIn() - returns a function type's input parameter count.
  // NumOut() - returns a function type's output parameter count.
  if (fn.Type().NumIn() != len(types)-1) || (fn.Type().NumOut() != 1) {return false}
  // In() - returns the type of a function type's i'th input parameter.
  for i := 0; i < len(types)-1; i++ {if fn.Type().In(i) != types[i] {return false}
  }
  // Out() - returns the type of a function type's i'th output parameter.
  outType := types[len(types)-1]
  if outType != nil && fn.Type().Out(0) != outType {return false}
  return true
}

退出类型判断的 Reduce

func Reduce(slice, pairFunc, zero interface{}) interface{} {sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {panic("reduce: wrong type, not slice")
  }

  len := sliceInType.Len()
  if len == 0 {return zero} else if len == 1 {return sliceInType.Index(0)
  }

  elemType := sliceInType.Type().Elem()
  fn := reflect.ValueOf(pairFunc)
  if !verifyFuncSignature(fn, elemType, elemType, elemType) {t := elemType.String()
    panic("reduce: function must be of type func(" + t + "," + t + ")" + t)
  }

  var ins [2]reflect.Value
  ins[0] = sliceInType.Index(0)
  ins[1] = sliceInType.Index(1)
  out := fn.Call(ins[:])[0]

  for i := 2; i < len; i++ {ins[0] = out
    ins[1] = sliceInType.Index(i)
    out = fn.Call(ins[:])[0]
  }
  return out.Interface()}

退出类型判断的 Filter

func Filter(slice, function interface{}) interface{} {result, _ := filter(slice, function, false)
  return result
}

func FilterInPlace(slicePtr, function interface{}) {in := reflect.ValueOf(slicePtr)
  if in.Kind() != reflect.Ptr {
    panic("FilterInPlace: wrong type," +
      "not a pointer to slice")
  }
  _, n := filter(in.Elem().Interface(), function, true)
  in.Elem().SetLen(n)
}

var boolType = reflect.ValueOf(true).Type()

func filter(slice, function interface{}, inPlace bool) (interface{}, int) {sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {panic("filter: wrong type, not a slice")
  }

  fn := reflect.ValueOf(function)
  elemType := sliceInType.Type().Elem()
  if !verifyFuncSignature(fn, elemType, boolType) {panic("filter: function must be of type func(" + elemType.String() + ") bool")
  }

  var which []int
  for i := 0; i < sliceInType.Len(); i++ {if fn.Call([]reflect.Value{sliceInType.Index(i)})[0].Bool() {which = append(which, i)
    }
  }

  out := sliceInType

  if !inPlace {out = reflect.MakeSlice(sliceInType.Type(), len(which), len(which))
  }
  for i := range which {out.Index(i).Set(sliceInType.Index(which[i]))
  }

  return out.Interface(), len(which)
}

总的来说就是验证 slice 类型,验证 function 类型和参数数量,验证 slice 元素类型与函数参数类型等等所有你能想到的失常代码中会报错的中央。

通过代码生成的形式生成出泛型代码

go genarate 命令能够辨认代码中的特定格局的正文,执行命令并传递参数,能够利用这个特点事后编写命令生成代码,防止在运行时应用反射或断言减少性能耗费,还能够放弃代码的简洁易读。

写一个生成通用容器的模板

package PACKAGE_NAME
type GENERIC_NAMEContainer struct {s []GENERIC_TYPE
}
func NewGENERIC_NAMEContainer() *GENERIC_NAMEContainer {return &GENERIC_NAMEContainer{s: []GENERIC_TYPE{}}
}
func (c *GENERIC_NAMEContainer) Put(val GENERIC_TYPE) {c.s = append(c.s, val)
}
func (c *GENERIC_NAMEContainer) Get() GENERIC_TYPE {r := c.s[0]
    c.s = c.s[1:]
    return r
}

针对模版做替换的脚本

#!/bin/bash

set -e

SRC_FILE=${1}
PACKAGE=${2}
TYPE=${3}
DES=${4}
#uppcase the first char
PREFIX="$(tr'[:lower:]''[:upper:]' <<< ${TYPE:0:1})${TYPE:1}"DES_FILE=$(echo ${TYPE}| tr'[:upper:]''[:lower:]')_${DES}.go

sed 's/PACKAGE_NAME/'"${PACKAGE}"'/g' ${SRC_FILE} | \
    sed 's/GENERIC_TYPE/'"${TYPE}"'/g' | \
    sed 's/GENERIC_NAME/'"${PREFIX}"'/g' > ${DES_FILE}

在理论代码中增加正文,正文格局为 go:generate command args,而后执行 go generate,会扫描 go 代码中的正文并执行。

//go:generate ./gen.sh ./template/container.tmp.go gen uint32 container
func generateUint32Example() {
    var u uint32 = 42
    c := NewUint32Container()
    c.Put(u)
    v := c.Get()
    fmt.Printf("generateExample: %d (%T)\n", v, v)
}

//go:generate ./gen.sh ./template/container.tmp.go gen string container
func generateStringExample() {
    var s string = "Hello"
    c := NewStringContainer()
    c.Put(s)
    v := c.Get()
    fmt.Printf("generateExample: %s (%T)\n", v, v)
}

最初生成两个容器代码

package gen

type Uint32Container struct {s []uint32
}
func NewUint32Container() *Uint32Container {return &Uint32Container{s: []uint32{}}
}
func (c *Uint32Container) Put(val uint32) {c.s = append(c.s, val)
}
func (c *Uint32Container) Get() uint32 {r := c.s[0]
    c.s = c.s[1:]
    return r
}
package gen

type StringContainer struct {s []string
}
func NewStringContainer() *StringContainer {return &StringContainer{s: []string{}}
}
func (c *StringContainer) Put(val string) {c.s = append(c.s, val)
}
func (c *StringContainer) Get() string {r := c.s[0]
    c.s = c.s[1:]
    return r
}

高阶函数之装璜器

func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Println("--->WithServerHeader()")
        w.Header().Set("Server", "HelloServer v0.0.1")
        h(w, r)
    }
}

func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Println("--->WithAuthCookie()")
        cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"}
        http.SetCookie(w, cookie)
        h(w, r)
    }
}

func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Println("--->WithBasicAuth()")
        cookie, err := r.Cookie("Auth")
        if err != nil || cookie.Value != "Pass" {w.WriteHeader(http.StatusForbidden)
            return
        }
        h(w, r)
    }
}

func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Println("--->WithDebugLog")
        r.ParseForm()
        log.Println(r.Form)
        log.Println("path", r.URL.Path)
        log.Println("scheme", r.URL.Scheme)
        log.Println(r.Form["url_long"])
        for k, v := range r.Form {log.Println("key:", k)
            log.Println("val:", strings.Join(v, ""))
        }
        h(w, r)
    }
}
func hello(w http.ResponseWriter, r *http.Request) {log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
    fmt.Fprintf(w, "Hello, World!"+r.URL.Path)
}

func main() {http.HandleFunc("/v1/hello", WithServerHeader(WithAuthCookie(hello)))
    http.HandleFunc("/v2/hello", WithServerHeader(WithBasicAuth(hello)))
    http.HandleFunc("/v3/hello", WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
    err := http.ListenAndServe(":8080", nil)
    if err != nil {log.Fatal("ListenAndServe:", err)
    }
}

利用 channel 实现 pipeline 模式

func echo(nums []int) <-chan int {out := make(chan int)
  go func() {
    for _, n := range nums {out <- n}
    close(out)
  }()
  return out
}

func sq(in <-chan int) <-chan int {out := make(chan int)
  go func() {
    for n := range in {out <- n * n}
    close(out)
  }()
  return out
}

func odd(in <-chan int) <-chan int {out := make(chan int)
  go func() {
    for n := range in {
      if n%2 != 0 {out <- n}
    }
    close(out)
  }()
  return out
}

func sum(in <-chan int) <-chan int {out := make(chan int)
  go func() {
    var sum = 0
    for n := range in {sum += n}
    out <- sum
    close(out)
  }()
  return out
}

var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for n := range sum(sq(odd(echo(nums)))) {fmt.Println(n)
}

或者独自一个 pipeline 函数,看着更适合

本文由 mdnice 多平台公布

退出移动版