驅(qū)動(dòng)開(kāi)發(fā)在Golang中的應(yīng)用

前言

在了解表驅(qū)動(dòng)開(kāi)發(fā)之前,有一個(gè)概念需要了解以下,那就是圈復(fù)雜度,又叫循環(huán)復(fù)雜度,那么什么是圈復(fù)雜度呢?

維基百科給出的解釋是:圈復(fù)雜度是用來(lái)度量程序復(fù)雜度的,與時(shí)間復(fù)雜度空間復(fù)雜度不同的是,圈復(fù)雜度是從程序的控制流程唯獨(dú)來(lái)進(jìn)行度量的,它指程序的控制流程圖中,若將結(jié)束點(diǎn)到起始點(diǎn)再增加一個(gè)邊時(shí),控制流程圖中圈(幾個(gè)邊形成的封閉路徑)的個(gè)數(shù)。

場(chǎng)景引入

幾乎每個(gè)系統(tǒng)中都少不了登錄功能,如果登錄模塊提供多種登錄方式(如微信、Apple、Google、用戶名/密碼、Token等),那么在代碼實(shí)現(xiàn)中你會(huì)怎么實(shí)現(xiàn)呢?相信很多人會(huì)采取如下方式:

type Platform uint8

const (
    Wechat Platform = iota + 1
    Apple
    Account
)

func Login(platform Platform, loginParam interface{}) (err error) {
    switch platform {
    case Wechat:
        return ByWechat(loginParam)
    case Apple:
        return ByApple(loginParam)
    case Account:
        return ByAccount(loginParam)
    default:
        // err
        return
    }
}

func ByWechat(param interface{}) (err error) {
    // logic
    return
}

func ByApple(param interface{}) (err error) {
    // logic
    return
}

func ByAccount(param interface{}) (err error) {
    // logic
    return
}

或者說(shuō)是定義一個(gè)登錄的方法集(接口),然后不同的方式定義不同的結(jié)構(gòu)體,每個(gè)結(jié)構(gòu)體實(shí)現(xiàn)登錄方法集,最后在統(tǒng)一的登錄入口處同樣通過(guò)switch來(lái)選擇不同的方法集載體。

盡管這樣實(shí)現(xiàn)沒(méi)問(wèn)題,但是值得思考的的一個(gè)點(diǎn)是:如果有更多的登錄方式,那么就需要在switch中添加更多的case,這樣下去的結(jié)果就是代碼難免會(huì)越來(lái)越顯得臃腫,對(duì)于功能復(fù)雜(代碼量大)的模塊來(lái)說(shuō)甚至越往后會(huì)越難維護(hù),那么如何采取一種看起來(lái)美觀,且易于維護(hù)的實(shí)現(xiàn)方式呢?

這里需要插一句,如果代碼中存在很多if,switch的話,會(huì)使代碼的圈復(fù)雜度上升,即讓代碼變得不那么可讀或者維護(hù)性不高。

如何解決?

此時(shí)我們可以通過(guò)表驅(qū)動(dòng)的方式來(lái)優(yōu)化該功能的實(shí)現(xiàn)。什么是表驅(qū)動(dòng)呢?顧名思義,就是通過(guò)(查)表的方法來(lái)改變舊有的邏輯(if...else/switch)語(yǔ)句,尤其是在業(yè)務(wù)中對(duì)于不同途徑的選擇存在大量的邏輯語(yǔ)句時(shí),可以考慮是否可以通過(guò)表驅(qū)動(dòng)的方法來(lái)實(shí)現(xiàn)。

那么對(duì)于上面提到的多種登錄功能,我們可以這樣實(shí)現(xiàn):

代碼目錄
  1. login.go
package login

import "errors"

type Platform uint8

const (
    Wechat Platform = iota + 1
    Apple
    Account
)

type ILogin interface {
    BeforeLogin(interface{})
    Login(interface{})
    AfterLogin(interface{})
}

var (
    m = make(map[Platform]ILogin)
)

func Register(platform Platform, method ILogin) {
    // 因?yàn)槭窃诿總€(gè)package中的init函數(shù)調(diào)用,所以不需要加鎖
    // 如果需要?jiǎng)討B(tài)添加,這里需要考慮并發(fā)
    m[platform] = method
}

func Login(platform Platform, param interface{}) (err error) {
    iface, ok := m[platform]
    if !ok {
        err = errors.New("invalid platform")
        return
    }
    iface.BeforeLogin(param)
    iface.Login(param)
    iface.AfterLogin(param)
    return
}

  1. account.go
package account

import "login"

type accountStruct struct {
    // field
}

func init() {
    login.Register(login.Account, &accountStruct{})
}

func (a *accountStruct) BeforeLogin(interface{}) {

}

func (a *accountStruct) Login(interface{}) {

}

func (a *accountStruct) AfterLogin(interface{}) {

}
  1. apple.go
package apple

import "login"

type appleStruct struct {
    // field
}

func init() {
    login.Register(login.Apple, &appleStruct{})
}

func (a *appleStruct) BeforeLogin(interface{}) {

}

func (a *appleStruct) Login(interface{}) {

}

func (a *appleStruct) AfterLogin(interface{}) {

}
  1. wechat.go
package wechat

import "login"

type wechatStruct struct {
    // field
}

func init() {
    login.Register(login.Wechat, &wechatStruct{})
}

func (a *wechatStruct) BeforeLogin(interface{}) {

}

func (a *wechatStruct) Login(interface{}) {

}

func (a *wechatStruct) AfterLogin(interface{}) {

}

這樣看起來(lái)代碼是否更加清晰直觀呢?如果需要添加更多的登錄方式只需要在新的package中實(shí)現(xiàn)對(duì)應(yīng)的API,同時(shí)在init函數(shù)中注冊(cè)對(duì)應(yīng)的登錄方式,即可在入口函數(shù)處調(diào)用!

最后

這種寫(xiě)法在grpc-go中也能找到。

資料參考

《Wiki百科》
《Go語(yǔ)言高級(jí)編程》

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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