OOP 思想在 TCC/APIX/GORM 源码中的应用

名词解释

OOP

面向方针程序规划(Object Oriented Programming,OOP)是一种计算机编程架构。OOP 的一条基本准则是计算机程序由单个能够起到子程序作用的单元或方针组函数调用语句合而成。OOP 到达了软件工程的三个主要方针:重用性、灵敏性和扩展性。面向方针编程的三大特色:封装性、继承性和多态性。

TCC

动态装备中心 TCC(ToutiaoConfigCenter)是供给给事务方的一套渠道+SD嵌套函数K 的装备办了处函数调用可以出现在表达式中吗理方案,供给的功用有权限办理、装备办理、版别办理、灰度发布、多区域多环境支撑等。与百度开源的“百度分布式装备中心 BRCC”功用相似。

APIX

Golang 完成的 web 结构,可参阅开源项目 Gin。

GO数组去重方法RM

Golang 编写的抢手数据库 ORM 结构。

背景

大力智能学习灯于 2019 年 10 月份上分页符怎么加入线,截止 2021 年底,台灯出货量已超越 100w 台,完成了从嵌套是什么意思 0 到 1 的探索。分页预览怎么关闭在成立之初,许多方向的产品为了尽早嵌套if函数拿到用户反嵌套查询应,要求快速迭代,研发在代码完成上相对快糙猛分页打印怎么设置,早期阶段这无可厚非,但嵌套是什么意思慢慢分页预览怎么重新分页地,自习室、体系东西、知识宇宙等运用已经变成灯上核心基建,假如软件开发还按之前的粗野成长的办法将会为台灯嵌套函数的成长埋下隐分页符和分节符的区别患。

在这样的背景下,大力智能服务端推进 OOP 技能专项的落地,希望嵌套是什么意思能够:提高团队成员本身的编码水平;一致团队内部编程风格;嵌套是什么意思支撑事务快速迭代。

TCC、A软件开发PIX、GORM 都是日常项目中经常会依靠到的外部包,本文从这些项目的源码出发,在学习的进程中,解读杰出的代码规划在其间的运用,希望能帮助咱们更好的了解和运用 OOP 思维,写出更优异的代码。

OOP 准则

单一责任准则(SRP)

一个类只负责一个责任(功用模块)。

敞开封闭准则(OCP)

数组个类、办法或模块的扩展性要保持敞开,可扩展但不影响源代码(封闭式更改)

替换准则(LSP)

子类能够替换父类,并且不会导致程序过错。

接口隔离准则(ISP)

一个类对另一个类的依靠应该建立在最小的接口上。

依靠倒置准则(DIP)

高层次的模块不应该依靠于低层次的模块,它们应该依靠于笼函数调用统。

参数可选,开箱即用—函数式选项形式

处理问题:在规划一个函数时,当存在装备参数较多,一起参嵌套函数数可选时,函数式选项形式是一个很好的选择,它既有为不熟悉的调用者准备好的默许装备,还有为需求定制的调用者供给自由修正装备的才能,且支撑未来灵敏扩展特点。

TCC 在创立BConfigClient方针时运用了该形式。BConfigClien软件商店t是用于发送 h数组初始化ttp 恳分页符求获取后端服务中 key软件商店 对应的 value 值函数调用可以出现在表达式中吗,其间getoptions结构体是 BConfigClient 的装备类,包含恳求的 cluster、addr、auth 等信息,小写最初,归于内部软件库结构体,不答应外部直接创立和修正,但一起对外供给了GetOption的办法去修正geto函数调用的三种方式ptions中的特点,其间WithClust函数调用的四个步骤erWi函数调用的四个步骤thAddrWithAuth分页预览怎么重新分页是方函数调用的三种方式便生成GetOption的函数。

这样的办法很好地操控了哪些特点能被外部修正,哪些是不可的。当getoptions需求添加新特点时,给定一个默许值,对应添加一个新GetOption办法即可,关于前史调用方来说无感,能向前兼容式的晋级,契合 OOP 中的对修正关闭,对扩展敞开的开闭规划准则。

typegetoptionsstruct{
clusterstring
addrstring
authbool
}
//GetOptionrepresentsoptionofgetop
typeGetOptionfunc(o*getoptions)
//WithClustersetsclusterofgetcontext
funcWithCluster(clusterstring)GetOption{
returnfunc(o*getoptions){
o.cluster=cluster
}
}
//WithAddrsetsaddrforhttprequestinsteadgetfromconsul
funcWithAddr(addrstring)GetOption{
returnfunc(o*getoptions){
o.addr=addr
}
}
//WithAuthSettheGDPRCertifyOn.
funcWithAuth(authbool)GetOption{
returnfunc(o*getoptions){
o.auth=auth
}
}

NewBConfigClient办法承受一个可变长度的GetOption,意味着调用者能够不必传任何参数,开箱即用,也能够依据自己的需求灵敏添加。函数内部首先初始化一个默许装备,然后循环履行GetOption办法,将用函数调用栈户界说的操作赋值给默许装备。

//NewBConfigClientcreatesinstanceofBConfigClient
funcNewBConfigClient(opts...GetOption)*BConfigClient{
oo:=getoptions{cluster:defaultCluster}
for_,op:=rangeopts{
op(&oo)
}
c:=&BConfigClient{oo:oo}
......
returnc
}

经过组合扩展功用—装修形式

处理问题:当已有类功用不行快捷时,经过组合的办法完成对已有类的功用扩展,完成了对已有代码的黑盒复用。

TCC 运用了装修形式扩展了本来已有的ClientV2的才能。分页符怎么显示出来

在下面的DemotionClient结构体中组合了ClientV2的引证,对外供给了GetIntGetBool两个办法,包掉了对原始 string 类型的转化函数调用中的参数太少,对外供给了更为快捷的办法。嵌套函数

//Get获取key对应的value.
func(c*ClientV2)Get(ctxcontext.Context,keystring)(string,error)
typeDemotionClientstruct{
*ClientV2
}
funcNewDemotionClient(serviceNamestring,config*ConfigV2)(*DemotionClient,error){
clientV2,err:=NewClientV2(serviceName,config)
iferr!=nil{
returnnil,err
}
client:=&DemotionClient{clientV2}
returnclient,nil
}
//GetIntparsevaluetoint
func(d*DemotionClient)GetInt(ctxcontext.Context,keystring)(int,error){
value,err:=d.Get(ctx,key)
iferr!=nil{
return0,err
}
ret,err:=strconv.Atoi(value)
iferr!=nil{
return0,fmt.Errorf("GetIntError:Key=%s;value=%sisnotint",key,value)
}
returnret,nil
}
//GetBoolparsevaluetobool:
//ifvalue=="0"returnfalse;
//ifvalue=="1"returntrue;
//ifvalue!="0"&&value!="1"returnerror;
func(d*DemotionClient)GetBool(ctxcontext.Context,keystring)(bool,error){
......
//相似GetInt办法
}

因为 Golang 言语对嵌入类型的支撑,DemotionClient在扩展才能的一起,ClientV2的本来办法也能正嵌套常调用,这样语法糖的规划让组合操作到达了继承的效果,且契合 OOP 中替换准则。

与 Java 言语对分页符怎么加入比,如下面的比如,类 A 和类 B 完成了IHi的接口,类 C 组合了接口IHi, 假如需求露出IHi的办法,则类 C 需分页符怎么加入求添加一个署理办法,这样 java 言语的组合在代码量上会数组去重多于继承办法,而 Golang 中无需分页预览怎么关闭额定数组排序代码即可供给支撑。

publicinterfaceIHi{
publicvoidhi();
}
publicclassAimplementsIHi{
@Override
publicvoidhi(){
System.out.println("Hi,IamA.");
}
}
publicclassBimplementsIHi{
@Override
publicvoidhi(){
System.out.println("Hi,IamB.");
}
}
publicclassC{
IHelloh;
publicvoidhi(){
h.hi();
}
}
publicstaticvoidmain(Stringargs[]){
Cc=newC();
c.h=newA();
c.hi();
c.h=newB();
c.hi();
}

躲藏杂乱方针结构进程—工厂形式

处理问题:将方针杂乱的结构逻辑躲藏在内部,调用者不必关怀细节,一起集中改变。

TC数组指针C 创立LogCounnter时运用了工厂形式,该类作用是依据函数调用语句过错日志呈现的频率判别是否需求打印日志,假分页符怎么取消掉如在指定的时间里,过错日志的触数组词发超越指定次数,则需求记载日志。

NewLogCounter办法经过入参 LogMode 枚举类型即可生成不同标准装备的LogCounter,能够无需再去了解 TriggerLo数组指针gCount、TriggerLogDuration、Enable 的含义。

typeLogModestring
const(
LowModeLogMode="low"
MediumModeLogMode="medium"
HighModeLogMode="high"
AlwaysModeLogMode="always"
ForbiddenModeLogMode="forbidden"
)
//InTriggerLogDuration,iferrortimes<TriggerLogCountpass,elseprinterrorlog.
typeLogCounterstruct{
FirstLogTimetime.Time
LogCountint
musync.RWMutex
TriggerLogCountint
TriggerLogDurationtime.Duration
Enablebool//IfEnableistrue,starttherule.
}
funcNewLogCounter(logModeLogMode,triggerLogCountint,triggerLogDurationtime.Duration)*LogCounter{
logCounter:=&LogCounter{}
switchlogMode{
caseAlwaysMode:
logCounter.Enable=false
caseLowMode:
logCounter.Enable=true
logCounter.TriggerLogCount=5
logCounter.TriggerLogDuration=60*time.Second
caseMediumMode:
logCounter.Enable=true
logCounter.TriggerLogCount=5
logCounter.TriggerLogDuration=300*time.Second
caseHighMode:
logCounter.Enable=true
logCounter.TriggerLogCount=3
logCounter.TriggerLogDuration=300*time.Second
caseForbiddenMode:
logCounter.Enable=true
logCounter.TriggerLogCount=0
}
iftriggerLogCount>0{
logCounter.Enable=true
logCounter.TriggerLogCount=triggerLogCount
logCounter.TriggerLogDuration=triggerLogDuration
}
returnlogCounter
}
func(r*LogCounter)CheckPrintLog()bool
func(r*LogCounter)CheckDiffTime(lastErrorTime,newErrorTimetime.Time)bool

识别改变隔离改变,简略工厂是一个清楚明了的完成办法。它契合了 DRY 准则(Don’t Repeat Yourself!),创立逻辑存放在单一的方位,即使它改变,也只需求修正一处就能够了。DRY 很简略,但却是确保咱们代码容易维护和复用的关键。DRY 准则一起还提示咱们:对体系功用进行杰出的切割,责任清晰的边界必定程度上确保了代码的单一性。[引证自 blog.51cto.com/weijie/8276…]

一步步构建杂乱方针—建造者形式

处理问题:运用多个简略的方针一步一步构建成一个杂乱的方针。

APIX分页符怎么显示出来 在创立恳求的匹配函数Matcher时运用了建造者形式。

APIX 中供给了指定对哪些 request 生效的中间件,界说和运用办法如下,CondHandle嵌套查询sql语句rsChain结构体中界说了匹配函数M软件商店atcher和命中后履行的处理函数HandlersChain

以“对途径前缀为/wechat 的恳求敞开微信认证中间件”为比软件工程如,Matcher分页符怎么显示出来 函数不必开发者从头完成一个,只需求初始化 SimpleMatcherBuilder 方针,设置恳求前缀后,直接 Build 出来即可,它将杂乱函数调用语句的匹配逻辑躲藏在内部,十分好用。

//Conditionalhandlerschain
typeCondHandlersChainstruct{
//匹配函数
Matcherfunc(method,pathstring)bool
//命中匹配后,履行的处理函数
ChainHandlersChain
}
//对途径前缀为`/wechat`的恳求敞开微信认证中间件
mw1:=apix.CondHandlersChain{
Matcher:new(apix.SimpleMatcherBuilder).PrefixPath("/wechat").Build(),
Chain:apix.HandlersChain{wxsession.NewMiddleware()},
}
//注册中间件
e.CondUse(mw1)

SimpleMatc嵌套虚拟化herBuilder是一个建造者,它完成了MatcherBuilder接口,该类支撑 method、pathPrefix分页符怎么显示出来 和 paths 三种匹配办法,事务方经过Method()PrefixPath(嵌套)FullPath()三个办法的组合软件调用即可结构出期望的匹配函数。

typeMatcherBuilderinterface{
Build()func(method,pathstring)bool
}
var_MatcherBuilder=(*SimpleMatcherBuilder)(nil)
//SimpleMatcherBuilderbuildamatcherforCondHandlersChain.
//An`AND`logicwillbeappliedtoallfields(ifprovided).
typeSimpleMatcherBuilderstruct{
methodstring
pathPrefixstring
paths[]string
}
func(m*SimpleMatcherBuilder)Method(methodstring)*SimpleMatcherBuilder{
m.method=method
returnm
}
func(m*SimpleMatcherBuilder)PrefixPath(pathstring)*SimpleMatcherBuilder{
m.pathPrefix=path
returnm
}
func(m*SimpleMatcherBuilder)FullPath(path...string)*SimpleMatcherBuilder{
m.paths=append(m.paths,path...)
returnm
}
func(m*SimpleMatcherBuilder)Build()func(method,pathstring)bool{
method,prefix:=m.method,m.pathPrefix
paths:=make(map[string]struct{},len(m.paths))
for_,p:=rangem.paths{
paths[p]=struct{}{}
}
returnfunc(m,pstring)bool{
ifmethod!=""&&m!=method{
returnfalse
}
ifprefix!=""&&!strings.HasPrefix(p,prefix){
returnfalse
}
iflen(paths)==0{
returntrue
}
_,ok:=paths[p]
returnok
}
}
var_MatcherBuilder=(AndMBuilder)(nil)
var_MatcherBuilder=(OrMBuilder)(nil)
var_MatcherBuilder=(*NotMBuilder)(nil)
var_MatcherBuilder=(*ExcludePathBuilder)(nil)
......

除此之外,ExcludePathBuilderAndMBuilderOrMBuilder*NotMBuilder也完成了MatcherBuilder接口,软件技术专业某些方针内部又嵌套了对Matche函数调用的方法rBuilder的调用,到达了多条件组合数组公式起来匹配的目的,十分灵敏。

//途径以`/api/v2`最初的恳求中,除了`/api/v2/legacy`外,都敞开中间件
mb1:=new(apix.SimpleMatcherBuilder).PrefixPath("/api/v2")
mb2:=new(apix.ExcludePathBuilder).FullPath("/api/v2/legacy")
mw3:=apix.CondHandlersChain{
Matcher:apix.AndMBuilder{mb1,mb2}.Build(),
Chain:apix.HandlersChain{...},
}
e.CondUse(mw1,mw2)

工厂办法形式 VS 建嵌套是什么意思造者形式

工厂办法形式重视的是全体方针的创立办法,而建造者形式重视的是部件构建的进程,旨在经过一步一步地准确结构创立出一个杂乱的方针。

举个简略比如来说明两者的差异,如要制作一个超人嵌套函数,假如运用工厂办法形式,软件技术专业直接产生出来的便是一个力大无穷、能够飞翔、内裤外穿的超人;而假如运用建造者形式,则需求组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。[引证自 www.cnblogs.com/ChinaHook/p…]

Web 中间件—责任链形式

处理问题:当事务处理流程很长时,可将一切恳求的处理者经过前一方针记住其下一个方针的引证而连成一条链;当有恳求产生时,可将恳求沿着这条链传递,直到没有方针处理它为止。

APIX 运用了责任链形式来完成中间件的功用,相似的逻辑可参阅文章“Gin 中间件的编写和运用”。

首先要界说中间件接口,即下文中的HandlerFunc,然后界说分页符怎么设置HandlersChain将一函数调用可以出现在表达式中吗组处理函数组合成一个处理链条,最后将HandlersChain刺进Context软件商店中。

开端履行时,是调用Context嵌套是什么意思Next函数,遍历每个HandlerFunc,然后将Context本身的引证传入,index是记载当时履行到第几个中间件,当进程中呈现不满足继续进行的条件时,能够调用Abort()来终止流程。

//界说中间件的接口
typeHandlerFuncfunc(*Context)
//将一组处理函数组合成一个处理链条
typeHandlersChain[]HandlerFunc
//处理的上下文
typeContextstruct{
//...
//handlers是一个包含履行函数的数组
//typeHandlersChain[]HandlerFunc
handlersHandlersChain
//index表示当时履行到哪个方位了
indexint8
//...
}
//Next会按照顺序将一个个中间件履行结束
//并且Next也能够在中间件中进行调用,到达恳求前以及恳求后的处理
func(c*Context)Next(){
c.index++
forc.index<int8(len(c.handlers)){
ifhandler:=c.handlers[c.index];handler!=nil{
handler(c)
}
c.index++
}
}
//中止中间件的循环,经过将索引后移到abortIndex完成。
func(c*Context)Abort(){
ifc.IsDebugging()&&c.index<int8(len(c.handlers)){
handler:=c.handlers[c.index]
handlerName:=nameOfFunction(handler)
c.SetHeader("X-APIX-Aborted",handlerName)
}
c.index=abortIndex
}

下面是一个查看用户是否登录的中间件完成,事务方也能够完成自函数调用是什么意思己的中间件刺进到恳求处理中,十分灵敏。

//RequireLogin查看用户是否登陆成功。假如不是,终止恳求。
funcRequireLogin(c*apix.Context){
ifc.Header(agwconsts.Key_LoaderSessionError)=="1"{
hsuite.AbortWithBizCode(c,bizstat.APIErrRPCFailed)
return
}
ifc.UserId()==0{
hsuite.AbortWithBizCode(c,bizstat.APIErrSessionExpired)
return
}
}

在服务发动时,注册中间件。

funcmain(){
e:=apiservice.Default(
hsuite.WithBizCodeErrs(consts.BizCodeErrs...),//user-definederrorcode
)
//可经过e.Use(),e.CondUse()注册中间件
e.Use(devicesession.AGWSessionSuccess,devicesession.NewHWSessionMiddleware(),middleware.Tracing)
......
apiservice.Run()
}

什么是洋葱模型

恳求进来时,一层一层的经过中间件履行Next函数进入到你设置的下一个中间件中,并且能够经过Co分页符怎么删除ntext方针一向向下传递下去,当到达最后一个中间件的时候软件开发,又向上回来到最初的当地。

该模型常用于记载恳求耗时、埋点等场景。函数调用栈

OOP 思维在 TCC/APIX/GORM 源码中的运用

在“Go 言语动手数组初始化写 Web软件开发 结构”[geektutu.com/post/gee-da… ]这篇文章中为咱们举了一个浅显易懂的比如。嵌套循环

假定咱们运用了中间件 A 和 B,和路由映射的 Handler。c.handlers是这分页符怎么显示出来样的[A, B, Handler],c.index初始化为-1。调用c.Ne函数调用的三种方式xt(),接分页预览怎么重新分页下来的流程是这样的:

funcA(c*Context){
part1
c.Next()
part2
}
funcB(c*Context){
part3
c.Next()
part4
}
-c.index++,c.index变为0
-0<3,调用c.handlers[0],即A
-履行part1,调用c.Next()
-c.index++,c.index变为1
-1<3,调用c.handlers[1],即B
-履行part3,调用c.Next()
-c.index++,c.index变为2
-2<3,调用c.handlers[2],即Handler
-Handler调用结束,回来到B中的part4,履行part4
-part4履行结束,回来到A中的part2,履行part2
-part2履行结束,结束。

终究的调用顺序是part1 -> part3 -> Handler -> part 4 -> part2

依靠注入,操控回转—观察者形式

处理问题:解耦观察者和被观察者,尤其是存在多个观察者的场景。

TCC 运用了观察者形式完成了当某 ke数组公式y 的 value数组去重 产生改变时履行回调的逻辑。

TccClient对外供给A数组初始化ddListener办法,答应事务注册对某 key 改变的监听,一起敞开定嵌套分类汇总怎么做时轮询,假如 key 的值与上次不同嵌套分类汇总的操作步骤就回调事务的 callb数组词ack 办法。

函数调用可以作为一个函数的形参儿的观察者是调用 AddListener 的发起者,被观察者是 TCC 的 key。Callback能够看作只需一个函数的接口,TccClient的通知函数调用中的参数太少回调不依靠于详细的完成,而是依靠于笼统,一起Call分页预览怎么重新分页back方针不是在内部构建的,而是在运行时传入的,让被观察者不再依靠观察者,经过依靠注入到达操控回转的目的。

//Callbackforlistener,外部监听者需求完成该办法传入,用于回调
typeCallbackfunc(valuestring,errerror)
//一个监听者实体
typelistenerstruct{
keystring
callbackCallback
lastVersionCodestring
lastValuestring
lastErrerror
}
//检测监听的key是否有产生改变,假如有,则回调callback函数
func(l*listener)update(value,versionCodestring,errerror){
ifversionCode==l.lastVersionCode&&err==l.lastErr{
return
}
ifvalue==l.lastValue&&err==l.lastErr{
//version_codeupdated,butvaluenotupdated
l.lastVersionCode=versionCode
return
}
deferfunc(){
ifr:=recover();r!=nil{
logs.Errorf("[TCC]listenercallbackpanic,key:%s,%v",l.key,r)
}
}()
l.callback(value,err)
l.lastVersionCode=versionCode
l.lastValue=value
l.lastErr=err
}
//AddListeneraddlistenerofkey,ifkey'svalueupdated,callbackwillbecalled
func(c*ClientV2)AddListener(keystring,callbackCallback,opts...ListenOption)error{
listenOps:=listenOptions{}
for_,op:=rangeopts{
op(&listenOps)
}
listener:=listener{
key:key,
callback:callback,
}
iflistenOps.curValue==nil{
listener.update(c.getWithCache(context.Background(),key))
}else{
listener.lastValue=*listenOps.curValue
}
c.listenerMu.Lock()
deferc.listenerMu.Unlock()
if_,ok:=c.listeners[key];ok{
returnfmt.Errorf("[TCC]listeneralreadyexist,key:%s",key)
}
c.listeners[key]=&listener
//一个client发动一个监听者
if!c.listening{
goc.listen()
c.listening=true
}
returnnil
}
//轮询监听
func(c*ClientV2)listen(){
for{
time.Sleep(c.listenInterval)
listeners:=c.getListeners()
forkey:=rangelisteners{
listeners[key].update(c.getWithCache(context.Background(),key))
}
}
}
//加锁防止多线程一起修正listeners,一起复制一份map在循环监听时运用。
func(c*ClientV2)getListeners()map[string]*listener{
c.listenerMu.Lock()
deferc.listenerMu.Unlock()
listeners:=make(map[string]*listener,len(c.listeners))
forkey:=rangec.listeners{
listeners[key]=c.listeners[key]
}
returnlisteners
}

什么是操控回转(Ioc—Inversion of Control)嵌套分类汇总怎么做

操控回转不是一种技能,仅仅一种思维,一个重要的面向数组和链表的区别方针编程的规律,它能指导咱们怎么规划出松耦合、更优良的程序,传统运用程序都是由咱们在类内部主动创软件工程师立依靠方针,然后导致类与类之间高耦合,难于测验;有了 IoC 容器后,把创立和查找依靠方针的操控权交给了容器,软件由容器进行注入组合方针,所以方针与方针之间是 松散耦函数调用合,这样也方便测验,利于功用复用,更数组指针重要的是使得程序的整个体系结构变得十分灵敏。

[引证自 blog.csdn.net/bestone0213…]

什么是依靠注入(DI—Dependency Injection)

组件之间依靠联系由容器在函数调用栈运行期决议,形象的说,即由容器动态的将某个依靠联系注入到组件之中。依靠软件工程师注入的目的并非为软件体系带来更多功用,而是为了提高组件重用的频率,并为体系建立一个灵敏、可扩展的渠道。经过依靠注入机制,咱们只需求经过简略的装备,而无需任何代码就可指定方针需求的资源,完成本身的事务逻辑,而不需求关怀详细的资嵌套源来自何嵌套分类汇总怎么做处,由谁完成。

[引证自 blog.csdn.net/bestone0213…]

操控回转和依靠注入是同一个概念的不同角度描述。简而言之,当依靠的外部组件时,不要直接从内部 new,而是从外部传入。

代替 IF—战略形式

处理场景:支撑不同战略的灵敏切换,避免多层操控句子的不高雅完成,避免呈现如下场景:

ifxxx{
//dosomething
}elseifxxx{
//dosomething
}elseifxxx{
//dosomething
}elseifxxx{
//dosomething
}else{
}

一般的做法是界说了一个函数调用中的参数太少公共接口,各种不同的算法以不同的办法完成这个接口,环境角色运用这个接口调用不同的算法软件测试

在 GORM 的 clause/clause.go 中运用到战略形式完成 SQL 的拼装。

现实事务中 SQL 句子千变万化,GORM 将 SQL 的拼接进程,拆分成了一个函数调用可以作为独立的语句存在个小的子句,这些子句一致完成clau分页符se.Interface这个接口,然后各自在Buil函数调用可以作为独立的语句存在d办法中完成自己的结构逻辑。

以最简略的分页查询为例,在运用 db 链式调用构建 SQL 时,对LimitOffsetOrder函数调用终究嵌套分类汇总的操作步骤转化成了Limit子句和OrderBy子句,两者都完成了clause.Interface接口。

db.WithContext(ctx).
Model(&Course{}).
Order("course_idDESC").
Limit(0).
Offset(100)
//Limitspecifythenumberofrecordstoberetrieved
func(db*DB)Limit(limitint)(tx*DB){
tx=db.getInstance()
tx.Statement.AddClause(clause.Limit{Limit:limit})
return
}
//Offsetspecifythenumberofrecordstoskipbeforestartingtoreturntherecords
func(db*DB)Offset(offsetint)(tx*DB){
tx=db.getInstance()
tx.Statement.AddClause(clause.Limit{Offset:offset})
return
}
//Orderspecifyorderwhenretrieverecordsfromdatabase
//db.Order("nameDESC")
//db.Order(clause.OrderByColumn{Column:clause.Column{Name:"name"},Desc:true})
func(db*DB)Order(valueinterface{})(tx*DB){
tx=db.getInstance()
switchv:=value.(type){
caseclause.OrderByColumn:
tx.Statement.AddClause(clause.OrderBy{
Columns:[]clause.OrderByColumn{v},
})
casestring:
ifv!=""{
tx.Statement.AddClause(clause.OrderBy{
Columns:[]clause.OrderByColumn{{
Column:clause.Column{Name:v,Raw:true},
}},
})
}
}
return
}

Cla分页符怎么显示出来use 的接口嵌套if函数界说:

//Interfaceclauseinterface
typeInterfaceinterface{
Name()string
Build(Builder)
MergeClause(*Clause)
}

Limit Clause 的界说:


//Limitlimitclause
typeLimitstruct{
Limitint
Offsetint
}
//Buildbuildwhereclause
func(limitLimit)Build(builderBuilder){
iflimit.Limit>0{
builder.WriteString("LIMIT")
builder.WriteString(strconv.Itoa(limit.Limit))
}
iflimit.Offset>0{
iflimit.Limit>0{
builder.WriteString("")
}
builder.WriteString("OFFSET")
builder.WriteString(strconv.Itoa(limit.Offset))
}
}
//Namewhereclausename
func(limitLimit)Name()string{......}
//MergeClausemergelimitbyclause
func(limitLimit)MergeClause(clause*Clause){......}

OrderBy Clause 的界说:

typeOrderByColumnstruct{
ColumnColumn
Descbool
Reorderbool
}
typeOrderBystruct{
Columns[]OrderByColumn
ExpressionExpression
}
//Buildbuildwhereclause
func(orderByOrderBy)Build(builderBuilder){
iforderBy.Expression!=nil{
orderBy.Expression.Build(builder)
}else{
foridx,column:=rangeorderBy.Columns{
ifidx>0{
builder.WriteByte(',')
}
builder.WriteQuoted(column.Column)
ifcolumn.Desc{
builder.WriteString("DESC")
}
}
}
}
//Namewhereclausename
func(limitLimit)Name()string{......}
//MergeClausemergeorderbyclause
func(limitLimit)MergeClause(clause*Clause){......}

下面的截图中列举了完成clause.Interface接口的一切类,以后 SQL 支撑新子句时,创立一个类完成c软件技术lau函数调用的方法se.Interfac函数调用是什么意思e接口,并在函数调数组初始化用的当地实例化该类,其他履行的代码皆可不变,契合 OOP 中的开闭准则和依靠倒置准则。

OOP 思维在 TCC/APIX/GORM 源码中的运用

Lazy 加载,线程安全—单例函数调用可以作为独立的语句存在形式

处理场景:变量只函数调用可以作为一个函数的形参想初始化一次。

APIX分页符 在埋点中间件中经过单例形式完成了对变量延迟且线程安全地赋值。

Metrics()用来生成 Metric 埋点中间件,在加载的进程,因为 APIX函数调用语句 的路软件开发由表还未注册结束,所以需求把两个变量 metricMap 和 pathMap 的初始化放嵌套循环在中间件的履行进程中,但服务器发动后,这软件开发两个变量的值是固定的,没必要重复初始化,函数调用可以作为一个函数的形参其次大量恳求过来时,中间件的逻辑会并发履行,存在线程不安全的问题。

故在完成的进程中用到了sync.Once软件商店方针,只需声明类型的 once 变量,就能够直接运分页符怎么删除用它的 Do 办法,Do 办法的参数是一个无参数,无回来的函数。

funcMetrics()HandlerFunc{
......
metricMap:=make(map[string]m3.Metric)//key:handlername
pathMap:=make(map[string][]string)//key:handlername,value:paths
once:=sync.Once{}//protectmapsinit
returnfunc(c*Context){
//whyinitinhandlerchainnotearlier:routeshaven'tbeenregisteredintotheenginewhenthemiddlewarefunccreated.
once.Do(func(){
for_,r:=rangec.engine.Routes(){
metricMap[r.Handler]=cli.NewMetric(r.Handler+".calledby",tagMethod,tagURI,tagErrCode,tagFromCluster,tagToCluster,tagFrom,tagEnv)
pathMap[r.Handler]=append(pathMap[r.Handler],r.Path)
}
})
c.Next()
......
}
}

Sync.Once

sync.分页符怎么删除Once的源码很短,它经过对一个标识值,原子性的修正和加载,来削减锁竞赛的。

typeOncestruct{
doneuint32
mMutex
}
func(o*Once)Do(ffunc()){
//加载标识值,判别是否已被履行过
ifatomic.LoadUint32(&o.done)==0{
o.doSlow(f)
}
}
func(o*Once)doSlow(ffunc()){//还没履行过函数
o.m.Lock()
defero.m.Unlock()
ifo.done==0{//doublecheck是否已被履行过函数
deferatomic.StoreUint32(&o.done,1)//修正标识值
f()//履行函数
}
}

它有两个特性,一是不论调用 Do 办法数组初始化多少次,里面的函数只会履行一次;二是假如开端有两个并发调用,能够确保第二个调用不会当即回来,会在获取锁的时候分页符怎么加入阻塞,等第一数组的定义个调用履行结束之后,第二个调用进行二次校验之后就直接回来了。

Sync.Once 有个问题,Do 的进程并不关注 f 函数履行的结果是成功还是失利,当 f()履行失利时,因为本身的机制,没有机会再次初始化了。假如你需求二次初始化,能够看看下面传送门中关于“函数调用可以作为一个函数的形参sync.Once 重试”的文章。

传送门

  • 百度分布式装备中心 B数组和链表的区别RCC:c分页符怎么删除loud.tencent.com/developer/a…
  • Gin github 地址:github.com/gin-gonic/g…
  • GORM 官网:gorm.io/zh_CN/docs/
  • Gin 中间件的编写和运用:www.moemona.com/2020/10/804…
  • Go 言语动手写 Web 结构:geektutu.com/post/gee-da…
  • sync.Once 重试:studygolang.com/articles/3数组排序1…

参阅和引证文献

  • blog.51cto.com/weijie/8276…
  • www.jianshu.com/p/150523d分页符怎么取消掉b2…
  • geektut嵌套函数u.com/post/gee-da…
  • www.musicpoet.top/20200409/7-…
  • www.cnblogs.com/ChinaHook/p…
  • blog.csdn.net/bestone0213…

发表回复

提供最优质的资源集合

立即查看 了解详情