八、Golang過(guò)程式編程

Golang過(guò)程式編程

1、接口型函數(shù)

用函數(shù)實(shí)現(xiàn)接口,這樣在調(diào)用的時(shí)候就會(huì)非常簡(jiǎn)便,這一類型的函數(shù)就叫做接口型函數(shù);使用情景:僅限于只有一個(gè)函數(shù)簽名的接口。

  • 原始使用,請(qǐng)看如下使用案例:

      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)
              }
          }
      }
    

上面案例,我們定義了僅有一個(gè)函數(shù)簽名Done (k , v interface{}) 的Handle類型接口,接著我們定義一個(gè)each函數(shù),接收兩個(gè)參數(shù):一個(gè)map、一個(gè)Handle類型,之后遍歷map, 調(diào)用Handle約束函數(shù)Done,操作map的key 和 value?,F(xiàn)在我們已經(jīng)定義好接口類型約束,接著我們要去使用了,請(qǐng)看如下使用案例:

    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{})
    }

仔細(xì)想想,以上使用案例有兩個(gè)有兩個(gè)缺陷:
a、因?yàn)榻涌谥皇且粋€(gè)約定的規(guī)則,不能直接使用,因此我們必須定義一個(gè)類型來(lái)實(shí)現(xiàn)這個(gè)接口,才能使用Handle約束的函數(shù)Done
b、既然是實(shí)現(xiàn)Handle類型接口,所以必須遵循Handle類型的約束規(guī)則,必須實(shí)現(xiàn)接口約束的方法Done。我們就不能夠隨心所欲的定義自己想要定義的更有意義的方法簽名

既然原始的接口使用方式,有上面的兩個(gè)缺陷,那么我們想要在約定的規(guī)則里,怎么才能隨心所欲的玩了,這就要我們的接口型函數(shù)來(lái)救場(chǎng)了

  • 接口型函數(shù)的使用,請(qǐng)看如下案例:

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

請(qǐng)看上面案例,因?yàn)镠andleFunc類型,是一個(gè)和Handle接口類型約束的方法Done簽名一樣的函數(shù)類型,因此我們可以把Done方法的調(diào)用以HandleFunc函數(shù)調(diào)用來(lái)替換,即通過(guò)調(diào)用Done方法,來(lái)調(diào)用自身【HandleFunc使用Done方法包裝自己】。

現(xiàn)在只要是和HandleFunc也即是和Done一樣簽名的函數(shù),就可以使用HandleFunc(yourFunc),來(lái)強(qiáng)制轉(zhuǎn)換我們自己定義的更有意義的函數(shù)為HandleFunc類型函數(shù),從而持有Done方法,而調(diào)用自己了。請(qǐng)看下面我們?nèi)绾蝸?lái)使用:

    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))
    }

現(xiàn)在我們的實(shí)現(xiàn)方式是不是更簡(jiǎn)單了,只要依據(jù)HandleFunc類型的簽名方式定義好我們自己想要定義的函數(shù),就可以直接使用了,而不用再去定義類型來(lái)實(shí)現(xiàn)Handle接口了,不過(guò)人都是愛(ài)偷懶的,每次調(diào)用時(shí)都要這樣HandleFunc(echoString)強(qiáng)制轉(zhuǎn)化,有沒(méi)有更好的方式了,請(qǐng)看下面我們的完整案例:

    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函數(shù)包裝優(yōu)化
    func EachFunc(m map[interface{}]interface{}, f func(k , v interface{})) {
        each(m, HandleFunc(f))
    }

    //接口型函數(shù)實(shí)現(xiàn)
    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)
    }

    //原始實(shí)現(xiàn)
    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)
    }

接口型函數(shù)總結(jié)起來(lái)就三點(diǎn)
a、聲明只有一個(gè)方法簽名Done的接口Handle
b、聲明一個(gè)和接口方法簽名相同的函數(shù)類型HandleFunc
c、使函數(shù)類型HandleFunc實(shí)現(xiàn)接口類型Handle


2、middleware 中間件

在使用中間件的時(shí)候,我們的明白什么是中間件?為什么要使用中間件?在那些場(chǎng)景下適合使用中間件

2.1. 什么是中間件
  首先,中間件的本質(zhì)就是:在執(zhí)行handler的前或者handler的后,先執(zhí)行一個(gè)自定義的handler而已,也就是要定義一個(gè)操作來(lái)包裝我們自定義的handler和我們要執(zhí)行的handler。
  
  在這里,就要提出兩個(gè)概念:
 ?。?、業(yè)務(wù)代碼:也就是我們主要執(zhí)行的邏輯代碼,也即是將要執(zhí)行的handler
 ?。狻⒎菢I(yè)務(wù)代碼:也就是在執(zhí)行業(yè)務(wù)代碼之前或者之后要執(zhí)行的公用代碼,也就是我們自定義的共有handler
  
2.2. 為什么要使用中間件
  既然明白了什么是中間件,那么我們就知道為什么要是用中間件了,使用中間件的目的就是剝離業(yè)務(wù)代碼和非業(yè)務(wù)代碼,實(shí)現(xiàn)解耦,從而減少遍地更改代碼的場(chǎng)景。
  列如:
  web業(yè)務(wù)的http請(qǐng)求,對(duì)于一個(gè)web項(xiàng)目我們會(huì)注冊(cè)很多路由,來(lái)匹配不同的業(yè)務(wù)場(chǎng)景,但是我們?nèi)绻獙?duì)請(qǐng)求耗時(shí)或者請(qǐng)求ip做統(tǒng)計(jì),就不能去更改每個(gè)路由對(duì)應(yīng)的業(yè)務(wù)代碼,而是把這一部分共有的操作作為非業(yè)務(wù)代碼剝離出來(lái),統(tǒng)一包裝執(zhí)行,這就要用到我們的中間件Middleware了
一般模式:

package main
import "fmt"

//中間件
type middleware func()

type ServiceHandle func()

//整合中間件和業(yè)務(wù)代碼
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()
}

//業(yè)務(wù)代碼
func ServiceCodeHandle() {
    fmt.Print("This is service code \n")
}

//非業(yè)務(wù)代碼
func NotServiceCodeHandle() {
    fmt.Print("This is not service code \n")
}

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

下面一http請(qǐng)求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 數(shù)組遍歷順序,和用戶希望的調(diào)用順序應(yīng)該是"相反"的.

3、validator 請(qǐng)求校驗(yàn)

使用第三方庫(kù)對(duì)請(qǐng)求參數(shù)做教研

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

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

type RegisterReq struct {
    // 字符串的 gt=0 表示長(zhǎng)度必須 > 0,gt = greater than
    Username        string   `validate:"gt=0"`
    // 同上
    PasswordNew     string   `validate:"gt=0"`
    // eqfield 跨字段相等校驗(yàn)
    PasswordRepeat  string   `validate:"eqfield=PasswordNew"`
    // 合法 email 格式校驗(yàn)
    Email           string   `validate:"email"`
}

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

4、golang如何實(shí)現(xiàn)插件化編程

代碼案例:

package main

import "fmt"

//聲明插件接口類型
type Plugin interface {
    String()
}

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

func (self *Plugins) init() {
    self.plist = []Plugin{}
}
//插件注冊(cè)
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、閉包

所謂閉包就是一個(gè)函數(shù)“捕獲”了和它在同一作用域的其他常量和變量。這就意味著閉包被調(diào)用的時(shí)候,不管程序在什么地方調(diào)用,閉包都能夠使用這些常量或者變量。只要閉包還存在,其所在作用域的變量或常量就存在

在go語(yǔ)言中所有匿名函數(shù)都是閉包。通常都是通過(guò)將一個(gè)閉包賦值給一個(gè)變量來(lái)使用閉包,或者將它放到一個(gè)數(shù)據(jù)結(jié)構(gòu)里(如映射或者切片),其主要的一種用法就是利用包裝函數(shù)來(lái)為被包裝函數(shù)預(yù)定一到多個(gè)參數(shù)。

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、遞歸函數(shù)

遞歸函數(shù)就是調(diào)用自己的函數(shù),遞歸函數(shù)通常都有相同的結(jié)構(gòu):
一個(gè)跳出條件:跳出條件就是一個(gè)條件語(yǔ)句,例如:if語(yǔ)句
一個(gè)遞歸體:遞歸體就是函數(shù)自身所做的一些處理

通常函數(shù)開(kāi)始處都會(huì)有一個(gè)跳出條件來(lái)確保遞歸能夠正常結(jié)束

注意:遞歸函數(shù)最少的調(diào)用自身一次,而且調(diào)用時(shí)所傳入的參數(shù)一定不能和當(dāng)前函數(shù)傳入的一樣。常用于二叉樹(shù)等,如下案例:

//返回一個(gè)包含n個(gè)數(shù)字的斐波那契數(shù)列
func Fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return Fibonacci(n-1) + Fibonacci(n-2)
}

7、運(yùn)行時(shí)候選擇函數(shù)

7.1 使用映射和函數(shù)引用來(lái)制造分支

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 動(dòng)態(tài)的創(chuàng)建函數(shù)
當(dāng)我們有兩個(gè)或者更多的函數(shù)實(shí)現(xiàn)了相同的功能時(shí),比如使用了不同的算法,我們不希望在編譯的時(shí)候靜態(tài)的綁定到其中任何一個(gè)函數(shù)(列如:允許我們動(dòng)態(tài)的選擇他們來(lái)做性能測(cè)試或者回歸測(cè)試)。

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 {
            //業(yè)務(wù)邏輯代碼
            return false
        }
    
    }else {
        HasProperties = func(s string) bool {
            //業(yè)務(wù)邏輯代碼
        
            return true
        }
    }
}

8、泛型函數(shù)

即所有類型都能用的函數(shù),golang不支持泛型函數(shù),只有我們自己在代碼中使用類型斷言或者反射來(lái)兼容泛型

9、高階函數(shù)

所謂高階函數(shù)就是講一個(gè)或者多個(gè)其它函數(shù)作為自己的參數(shù),并在函數(shù)體里調(diào)用他們

10、純記憶函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容