前言
在了解表驅(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、用戶(hù)名/密碼侨拦、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):
- 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
}
- 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{}) {
}
- 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{}) {
}
- 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中也能找到酬蹋。