八、Golang过程式编程

Golang过程式编程

1、接口型函数

用函数实现接口,这样在调用的时候就会非常简便,这一类型的函数就叫做接口型函数;使用情景:仅限于只有一个函数签名的接口。

  • 原始使用,请看如下使用案例:

      type Handle interface {
          Done (k , v interface{})
      }
    
      func each(m map[interface{}]interface{}, h Handle) {
          if m != nil && len(m) > 0 {
              for k, v := range m {
                  h.Done(k, v)
              }
          }
      }
    

上面案例,我们定义了仅有一个函数签名Done (k , v interface{}) 的Handle类型接口,接着我们定义一个each函数,接收两个参数:一个map、一个Handle类型,之后遍历map, 调用Handle约束函数Done,操作map的key 和 value。现在我们已经定义好接口类型约束,接着我们要去使用了,请看如下使用案例:

    type student struct {}
    func (self *student) Done(k, v interface{}) {
        fmt.Printf("student's name is: %s, age is: %d \n", k, v)
    }

    func main(){
        m := make(map[interface{}]interface{})
        m["sampsom"] = 25
        m["jiny"] = 30
        each(m, &student{})
    }

仔细想想,以上使用案例有两个有两个缺陷:
a、因为接口只是一个约定的规则,不能直接使用,因此我们必须定义一个类型来实现这个接口,才能使用Handle约束的函数Done
b、既然是实现Handle类型接口,所以必须遵循Handle类型的约束规则,必须实现接口约束的方法Done。我们就不能够随心所欲的定义自己想要定义的更有意义的方法签名

既然原始的接口使用方式,有上面的两个缺陷,那么我们想要在约定的规则里,怎么才能随心所欲的玩了,这就要我们的接口型函数来救场了

  • 接口型函数的使用,请看如下案例:

      type Handle interface {
          Done (k , v interface{})
      }
    
      type HandleFunc func(f , v interface{})
    
      func (f HandleFunc) Done(k , v interface{}) {
          h(k, v)
      }
    

请看上面案例,因为HandleFunc类型,是一个和Handle接口类型约束的方法Done签名一样的函数类型,因此我们可以把Done方法的调用以HandleFunc函数调用来替换,即通过调用Done方法,来调用自身【HandleFunc使用Done方法包装自己】。

现在只要是和HandleFunc也即是和Done一样签名的函数,就可以使用HandleFunc(yourFunc),来强制转换我们自己定义的更有意义的函数为HandleFunc类型函数,从而持有Done方法,而调用自己了。请看下面我们如何来使用:

    func echoString(k , v interface{}) {
        fmt.Printf("student's name is: %s, age is: %d \n", k, v)
    }

    func main(){
        m := make(map[interface{}]interface{})
        m["sampsom"] = 25
        m["jiny"] = 30
        each(m, HandleFunc(echoString))
    }

现在我们的实现方式是不是更简单了,只要依据HandleFunc类型的签名方式定义好我们自己想要定义的函数,就可以直接使用了,而不用再去定义类型来实现Handle接口了,不过人都是爱偷懒的,每次调用时都要这样HandleFunc(echoString)强制转化,有没有更好的方式了,请看下面我们的完整案例:

    package main

    import "fmt"

    type Handle interface {
        Done (k , v interface{})
    }

    func each(m map[interface{}]interface{}, h Handle) {
        if m != nil && len(m) > 0 {
            for k, v := range m {
                h.Done(k, v)
            }
        }
    }

    //each函数包装优化
    func EachFunc(m map[interface{}]interface{}, f func(k , v interface{})) {
        each(m, HandleFunc(f))
    }

    //接口型函数实现
    type HandleFunc func(k , v interface{})

    func (h HandleFunc) Done(k , v interface{}) {
        h(k, v)
    }

    func echoString(k , v interface{}) {
        fmt.Printf("student's name is: %s, age is: %d \n", k, v)
    }

    //原始实现
    type student struct {}

    func (self *student) Done(k, v interface{}) {
        fmt.Printf("student's name is: %s, age is: %d \n", k, v)
    }

    func main(){
        m := make(map[interface{}]interface{})
        m["sampsom"] = 25
        m["jiny"] = 30
        EachFunc(m, echoString)
    }

接口型函数总结起来就三点:
a、声明只有一个方法签名Done的接口Handle
b、声明一个和接口方法签名相同的函数类型HandleFunc
c、使函数类型HandleFunc实现接口类型Handle


2、middleware 中间件

在使用中间件的时候,我们的明白什么是中间件?为什么要使用中间件?在那些场景下适合使用中间件

2.1. 什么是中间件
  首先,中间件的本质就是:在执行handler的前或者handler的后,先执行一个自定义的handler而已,也就是要定义一个操作来包装我们自定义的handler和我们要执行的handler。
  
  在这里,就要提出两个概念:
  a、业务代码:也就是我们主要执行的逻辑代码,也即是将要执行的handler
  b、非业务代码:也就是在执行业务代码之前或者之后要执行的公用代码,也就是我们自定义的共有handler
  
2.2. 为什么要使用中间件
  既然明白了什么是中间件,那么我们就知道为什么要是用中间件了,使用中间件的目的就是剥离业务代码和非业务代码,实现解耦,从而减少遍地更改代码的场景。
  列如:
  web业务的http请求,对于一个web项目我们会注册很多路由,来匹配不同的业务场景,但是我们如果要对请求耗时或者请求ip做统计,就不能去更改每个路由对应的业务代码,而是把这一部分共有的操作作为非业务代码剥离出来,统一包装执行,这就要用到我们的中间件Middleware了
一般模式:

package main
import "fmt"

//中间件
type middleware func()

type ServiceHandle func()

//整合中间件和业务代码
type Merge struct {
    middlewareChain [] func()
    serviceHandler ServiceHandle
}

func (self *Merge)Use(m middleware) {
    self.middlewareChain = append(self.middlewareChain, m)
}

func (self *Merge)Add(sh ServiceHandle) {
    self.serviceHandler = sh

    for _, m := range self.middlewareChain {
        m()
    }

    self.serviceHandler()
}

//业务代码
func ServiceCodeHandle() {
    fmt.Print("This is service code \n")
}

//非业务代码
func NotServiceCodeHandle() {
    fmt.Print("This is not service code \n")
}

func main(){
    m := new(Merge)
    m.Use(NotServiceCodeHandle)
    m.Add(ServiceCodeHandle)
}

下面一http请求router举例:

type middleware func(http.Handler) http.Handler

type Router struct {
    middlewareChain [] func(http.Handler) http.Handler
    mux map[string] http.Handler
}

func NewRouter() *Router{
    return &Router{}
}

func (r *Router) Use(m middleware) {
    r.middlewareChain = append(r.middlewareChain, m)
}

func (r *Router) Add(route string, h http.Handler) {
    var mergedHandler = h

    for i := len(r.middlewareChain) - 1; i >= 0; i-- {
    mergedHandler = r.middlewareChain[i](mergedHandler)
    }

    r.mux[route] = mergedHandler
}

使用:

r = NewRouter()
r.Use(logger)
r.Use(timeout)
r.Use(ratelimit)
r.Add("/", helloHandler)

注意代码中的 middleware 数组遍历顺序,和用户希望的调用顺序应该是"相反"的.

3、validator 请求校验

使用第三方库对请求参数做教研

https://github.com/go-playground/validator

import "gopkg.in/go-playground/validator.v9"

type RegisterReq struct {
    // 字符串的 gt=0 表示长度必须 > 0,gt = greater than
    Username        string   `validate:"gt=0"`
    // 同上
    PasswordNew     string   `validate:"gt=0"`
    // eqfield 跨字段相等校验
    PasswordRepeat  string   `validate:"eqfield=PasswordNew"`
    // 合法 email 格式校验
    Email           string   `validate:"email"`
}

func validate(req RegisterReq) error {
    err := validate.Struct(mystruct)
    if err != nil {
    doSomething()
    }
    ...
}

4、golang如何实现插件化编程

代码案例:

package main

import "fmt"

//声明插件接口类型
type Plugin interface {
    String()
}

//插件集合
type Plugins struct {
    plist []Plugin
}

func (self *Plugins) init() {
    self.plist = []Plugin{}
}
//插件注册
func (self *Plugins) Register(p Plugin){
    self.plist = append(self.plist, p)
}

type Student struct {
    name string
    sex string
    age int
}

func (self *Student) String () {
    fmt.Printf("Student's name is: %s, sex is: %s, age is: %d \n", self.name, self.sex, self.age)
}

func main(){
    plugins := new(Plugins)
    plugins.init()
    p := &Student{
        name: "sampson",
        sex: "man",
        age: 28,
    }

    plugins.Register(p)

    for _, p := range plugins.plist{
        p.String()
    }
}

5、闭包

所谓闭包就是一个函数“捕获”了和它在同一作用域的其他常量和变量。这就意味着闭包被调用的时候,不管程序在什么地方调用,闭包都能够使用这些常量或者变量。只要闭包还存在,其所在作用域的变量或常量就存在

在go语言中所有匿名函数都是闭包。通常都是通过将一个闭包赋值给一个变量来使用闭包,或者将它放到一个数据结构里(如映射或者切片),其主要的一种用法就是利用包装函数来为被包装函数预定一到多个参数。

type Student struct {
    name string
    sex string
    age int
}

func string (s Student) func (){
    return func() {
        fmt.Printf("Student's name is: %s, sex is: %s, age is: %d \n", s.name, s.sex, s.age)
    }
}

6、递归函数

递归函数就是调用自己的函数,递归函数通常都有相同的结构:
一个跳出条件:跳出条件就是一个条件语句,例如:if语句
一个递归体:递归体就是函数自身所做的一些处理

通常函数开始处都会有一个跳出条件来确保递归能够正常结束

注意:递归函数最少的调用自身一次,而且调用时所传入的参数一定不能和当前函数传入的一样。常用于二叉树等,如下案例:

//返回一个包含n个数字的斐波那契数列
func Fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return Fibonacci(n-1) + Fibonacci(n-2)
}

7、运行时候选择函数

7.1 使用映射和函数引用来制造分支

func entry() {
    var bi BusinessInstance
    switch businessType {
    case TravelBusiness:
        bi = travelorder.New()
    case MarketBusiness:
        bi = marketorder.New()
    default:
        return errors.New("not supported business")
    }
}

改为:

var businessInstanceMap = map[int]BusinessInstance {
    TravelBusiness : travelorder.New(),
    MarketBusiness : marketorder.New(),
}

func entry() {
    bi := businessInstanceMap[businessType]
}

7.1 动态的创建函数
当我们有两个或者更多的函数实现了相同的功能时,比如使用了不同的算法,我们不希望在编译的时候静态的绑定到其中任何一个函数(列如:允许我们动态的选择他们来做性能测试或者回归测试)。

var HasProperties func (string) bool

func init() {
    if len(os.Args) >1 && (os.Args[1] == "-a" || os.Args[1] == "--asscii") {
        os.Args = append(os.Args[:1], os.Args[2:]...)
        HasProperties = func(s string) bool {
            //业务逻辑代码
            return false
        }
    
    }else {
        HasProperties = func(s string) bool {
            //业务逻辑代码
        
            return true
        }
    }
}

8、泛型函数

即所有类型都能用的函数,golang不支持泛型函数,只有我们自己在代码中使用类型断言或者反射来兼容泛型

9、高阶函数

所谓高阶函数就是讲一个或者多个其它函数作为自己的参数,并在函数体里调用他们

10、纯记忆函数

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容