0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么要设计模式

马哥Linux运维 来源:云原生CTO 作者:charts 2022-06-14 11:08 次阅读

GoRustPythonIstiocontainerdCoreDNSEnvoyetcdFluentdHarborHelmJaegerKubernetesOpenPolicyAgentPrometheusRookTiKVTUFVitessArgoBuildpacksCloudEventsCNIContourCortexCRI-OFalcoFluxgRPCKubeEdgeLinkerdNATSNotaryOpenTracingOperatorFrameworkSPIFFESPIREThanos


Go 中的构建器模式

Operator 条件更新上应用 Go 风格的构建器模式的实际示例

建议我们在某个“框架”内进行编码,即遵循一定的设计模式,这些模式是有效的、可复制的、被广泛认可的、更容易理解和应用的。

为什么要设计模式

为了不那么抽象,我们从实践中的一个例子开始。

通常,我们定义一个struct,然后在使用它时对其进行初始化。

typeAstruct{
namestring
}
a:=A{name:“abc”}

这是常见的用法,但不适用于A复杂的场景

  • 多层嵌套字段
  • 超过 5 个字段
  • 不同的字段需要不同的默认值
  • 多个可选字段
  • 以上四种的组合

例如在Kubernetes operator开发中,我们调用SetStatusAndCondition来更新资源信息,其中不仅包含了metav1的基本信息。条件,如状态,原因,观察生成等,但也传递回调函数,如OnSuccessOnError。围绕ConditionAndStatus,我们可以添加其他逻辑,比如发送事件、处理不同状态(成功或失败)的逻辑,等等,然后定义一个类似如下的结构。

typeConditionAndStatusstruct{
Conditionmetav1.Condition
EventTypestring//eventtype
Recorderrecord.EventRecorder,//K8seventsrecorder
Forcebool,//isForceupdate
OnErrorfunc,//errhandler
OnSuccesfunc,//successhandler
}

它可以通过通过new初始化这个ConditionAndStatus来工作,但是当有超过5个字段并且其中两个是嵌套的时候,它是累赘和复杂的,这是对非调用者友好的,并且在代码可读性上很差。除非conditioneventRecorder被实例化,否则调用者不能实例化ConditionAndStatus。调用者需要知道所有的实现细节,例如,他们应该知道在错误处理中更新条件时传递onSucc方法,即使只有nil。此外,不同的用户在不同的地方执行初始化时,每次都需要传入相同的onSucconErr

那么我们该如何优化这段代码呢?

Factory 模式

应用Factory模式可能是我们想到的第一个想法,但它不适用于这种情况。

5e4ce432-eb1e-11ec-ba43-dac502259ad0.png

通过Factory模式封装一些创建方法。

//Createnofalse,nodefaulthandlers
func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder)ConditionAndStatus{
returncreate(cond,eventType,recorder,false,nil,nil)
}

//Createnodefaulthandlers
func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,forcebool)ConditionAndStatus{
returncreate(cond,eventType,recorder,force,nil,nil)
}

//...morecreatefunctions

func(cConditionAndStatus)Create(condmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,forcebool,onErrfunc,onSuccfunc)ConditionAndStatus{
returnConditionAndStatus{
condtion:cond,
eventType:eventType,
recorder:recorder,
force:force,
onErr:onErr,
onSucces:onSucc,
}
}

api应该易于使用且不易误用——来自Josh Bloch

然而,Factory模式实现的api并不是那么方便。

显然,create不是一个选项,因为它需要提供所有参数,传入的参数越多,操作就越困难。此外,当多个参数为同一类型时,很容易出错。

尽管其他Factory方法可以通过提供一些默认值来减少传入的参数来降低复杂性,但它们仍然缺乏灵活性。添加参数后,需要修改所有create *方法。

Builder模式

Builder模式是一种设计模式,旨在为面向对象编程中的各种对象创建问题提供灵活的解决方案

来自https://en.wikipedia.org/wiki/Builder_pattern。

构建器模式为灵活简化复杂对象的创建铺平了道路,同时也隐藏了嵌入式类型的一些初始化细节,大大提高了可读性。

Builder接口

builder接口是两种builder模式实现之一,buildxxx用接口实现各个字段的方法,Builder通过多态性确定具体的builder。请参阅下面的 UML 流程图。

5e5c6420-eb1e-11ec-ba43-dac502259ad0.png

让我们“翻新”以前的ConditionAndStatus.

typeConditionAndStatusBuilderinterface{
SetCondtion(condmetav1.Condition)ConditionAndStatusBuilder
SetEventType(evnetTypestring)ConditionAndStatusBuilder
SetRecorder(recorderrecord.EventRecorder)ConditionAndStatusBuilder
SetForce(forcebool)ConditionAndStatusBuilder
SetOnErr(onErrfunc())ConditionAndStatusBuilder
SetOnSuccess(onSuccfunc())ConditionAndStatusBuilder
Build()ConditionAndStatus
}

typeDefaultBuilderstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}

func(b*DefaultBuilder)SetCondtion(condmetav1.Condition)DefaultBuilder{
b.condition=cond
returnb
}
//...moresetfuncs

func(b*DefaultBuilder)Build()ConditionAndStatus{
//setsomedefaultvalues
b.force=true
returnConditionAndStatus{
condition:b.condtion,
//...
}
}

要创建ConditionAndStatus,您可以使用注册方法组成所有构建器,然后通过getByName获得特定的构建器。

不难得出结论,该模式与Factory模式非常相似,因为每个构建器仍然需要创建所有字段或提供默认值。但它确实向前迈出了一步。

  • 当字段确定时,它可以灵活地添加新的生成器,而不需要修改旧的生成器。
  • 它可以控制创建不同字段的顺序。如果字段是相互依赖的,它可以隐藏细节并防止调用者犯错误。

然而,它与Factory模式有相同的缺点:一旦添加了字段(在Builder接口中添加方法),就需要修改所有构建器。

Pipeline建设者

另一种构建器模式是管道构建器,它更常见。通过上面的接口builder,你会发现多builder的设计是多余的,而让调用者控制相关字段的分配更合理:唯一的一个builder管理所有字段初始化,并通过返回builder本身来构建管道在每一步中,最后都组装成我们想要的。

5e8f8990-eb1e-11ec-ba43-dac502259ad0.png

通用调用代码的格式为obj.Withxxx().Withyyy().Withzzz().build(). 更改ConditionAndStatus如下。

typeBuilderstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}

func(b*Builder)WithCondition(condmetav1.Condition)Builder{
b.condition=cond
returnb
}
//...moreWithxxxfuncs

func(b*Builder)Build()ConditionAndStatus{
//setsomedefaultvalues
b.force=true
returnConditionAndStatus{
condition:b.condtion,
//...
}
}

Pipeline builder巧妙地避免了添加新字段带来的麻烦。只有一个builder,它可以通过添加With*方法轻松处理字段添加。

它对现有的调用者绝对更友好。如果参数是可选的,则不需要修改其他调用者的代码。而你只有通过添加新的调用者并With*在调用时插入方法来完成它;但是,当需要新参数而没有默认值时,则需要修改所有调用者的代码。

当然,没有一种模式是没有缺陷的,管道构建器也不是。

  • Withxxx()一旦要构建许多字段,堆积的方法会给调用者带来麻烦并降低可读性。
  • 无法控制字段的初始化顺序。如果存在依赖关系,则需要出色的错误控制和文档来避免错误。
  • 代码不是 Go 风格,而是更多 Java 风格。

可选的构建器模式

如果我们进一步优化管道构建器会怎样?正如Dave Cheney在他的Practical Go中提到的那样,我们应该以更多 Go 的方式尝试它。

首选 var args[]T 参数

深入挖掘,我们看到这里的大部分字段都是可选的,并且可以var args自然地定义。如有传入,申报;如果没有,请忽略它。因此,builder/factory当隐藏实现细节时,只需要一种方法来处理整个对象的创建。

让我们一步一步地把这个想法付诸实践。

将可选字段抽象到构建结构中,而不是将所有字段都放入。要将ConditionAndStatus转换为以下结构,其中配置包含所有可选字段。

typeConditionAndStatusstruct{
conditionmetav1.Condition
eventTypestring//eventtype
recorderrecord.EventRecorder,//K8seventsrecorder
configsconfigs//Optionalconfigs
}

typeconfigsstruct{
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}

对于配置,使用func选项接受一个*configs并返回自身以集成到管道中。需要使用以下方法。

typeconfigsstruct{
forcebool,//isForceupdate
onErrorfunc,//errhandler
onSuccesfunc,//successhandler
}

typeOptionfunc(*configs)

funcForceUpdate(forcebool)Option{returnfunc(c*configs){c.force=force}}

funcOnErr(onErrfunc())Option{returnfunc(c*configs){c.onErr=onErr}}

funcOnSuccess(onSuccfunc())Option{returnfunc(c*configs){c.onSuccess=onSucc}}

然后是新的create方法,包括必要字段和可选配置的初始化。因为所有可选的配置都是用func类型的返回值初始化的,所以整个配置的赋值只能用一个循环来完成。超级简洁!

funcCreate(conditionmetav1.Condition,eventTypestring,recorderrecord.EventRecorder,os...Option)error{
opts:=configs{
force:false,
onSuccess:func(){},
onError:nil,
}
//Applyalltheoptionalconfigs
for_,applyOption:=rangeos{
applyOption(&opts)
}
//checkrequiredfields

//updateconditionshere

//handleerr
ifopts.err!=nil{
returnopts.onError()
}

//eveutallycallsuccessfunc
opts.onSuccess()
}

调用方可以根据场景选择可选配置,避免误用。

setCondition(
metav1.Condition{
Type:apis.Ready,
Status:metav1.ConditionFalse,
Reason:apis.UpstreamUnavailable,
Message:fmt.Sprintf("Failedtosetresources%#v",resource),
},
"Update",
nil,
//onlyneedonErrfuncfromtheoptionalconfigs.
conditionAndStatus.ForOnErr(err),
)

Builder in Kubernetes

Kubernetes源代码的几乎每个角落都可以看到这种go风格的代码。几乎所有的结构被*配置是建立在可选的建造者模式,如PodApplyConfiguration EventApplyConfiguration和配置文档你找到包裹。这些逐层嵌套配置获得最终值与一个或多个方法类似于PodApplyConfiguration提取。

最后

设计模式是经典的,尽管不是所有的模式都能在Go中完美实现。Builder无疑是其中最杰出的一个,我们应该最大限度地利用Optional管道生成器模式来构建一些配置对象,特别是在设计公共模块时。使用灵活、遵守代码标准和扩展友好的api,可以大大减轻升级压力。

感谢你的阅读!

原文标题:Go 中的构建器模式

文章出处:【微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 源代码
    +关注

    关注

    96

    文章

    2943

    浏览量

    66602
  • 设计模式
    +关注

    关注

    0

    文章

    53

    浏览量

    8618
  • Struct
    +关注

    关注

    0

    文章

    31

    浏览量

    10846
  • 云原生
    +关注

    关注

    0

    文章

    240

    浏览量

    7929

原文标题:Go 中的构建器模式

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    为什么官方的移植DEMO下载到FLASH?STlink仿真模式问题?

    我最近在做STM32F107移植uCOSii,遇到几个问题,为什么官方的移植DEMO下载到FLASH?在用STlink仿真时,使用JATG模式出现错误,而使用SWD就正常运行呢?备注:编译器
    发表于 03-27 15:04

    请问4通道都用DMA模式怎么使用

    我有4个AD要用到,想用DMA模式,看库函数例程只有一个通道,请问4通道都用DMA模式怎么使用
    发表于 09-26 09:23

    在DCM下的ip要比CCM模式下的ip大出不少

    在DCM下的ip要比CCM模式下的ip大出不少的,同等条件下CCM模式有效值电流更低。有没有详细的推导公式假设输出功率相同,开关频率相同,同样的输入电压
    发表于 10-23 11:01

    CAN初始化,CAN实验初始化,CAN_DeInit()这句话不写可以吗

    一些实验,加了缺省设置,然后自己又配置了。感觉没必要,CAN实验初始化,CAN_DeInit()这句话不写也行把但很多实验都这样写,有什么好处?缺省设置不一定是你模式
    发表于 05-31 04:35

    STM32 IO口设置成什么模式

    stm32的部分管脚可以兼容5V,那么请问当外接信号为5V作为输入时,IO口设置成什么模式
    发表于 05-29 10:13

    为什么DAC端口设置为模拟输入模式

    的 DAC 通道 1 在 PA4 上,所以,我们先要使能 PORTA 的时钟,然后设置 PA4 为模拟输入。DAC 本身是输出,但是为什么端口设置为模拟输入模式呢?因为一但使能 DACx 通道之后
    发表于 08-17 07:22

    什么是设计模式?为什么学习设计模式

    物是人非事事休,当周围的一切都发生着改变,包括我们的需求、程序等,我们又该如何去应对和解决呢?欢迎进入编程人员必经之路------设计模式1 本篇概述什么是设计模式为什么学习设计模式
    发表于 01-19 06:41

    CH9141默认的应该是从机模式,是设置成主机模式才能连接吗?

    CH9141默认的应该是从机模式,是设置成主机模式才能连接吗?
    发表于 09-21 07:33

    ESP32从AP切换到STA模式重启么?

    ESP32上电先跑STA模式,接收到指令后切换AP模式进入UDP服务接收SSID PASSWORD,写入NVS后再切回STA模式连接AP失败怎么搞?STA模式下都是从NVS读取
    发表于 03-09 06:22

    性能模式和省电模式的区别是什么

    大家都知道,Long long agoEMUI就有了不同的应用模式,是为了兼顾不同的使用场景而为用户设置的,例如玩游戏时需要性能强,就需要性能模式,出差在外时,尽量续航长,就选择省电模式
    发表于 07-13 08:47 1w次阅读

    什么是框架?MATLAB的单元测试框架中文版资料详细概述

    从逻辑上来说,框架 (Framework),是⼀个⽐⾯向对象和设计模式更加复杂的结构,但读者不⽤担⼼,虽然框架在结构上⽐模式复杂,但是学习起来⽐设计
    发表于 11-18 08:00 0次下载
    什么是框架?MATLAB的单元测试框架中文版资料详细概述

    手机双屏模式如何设置

    最近小编发现有诸多的小伙伴们对于手机双屏模式如何设置都颇为感兴趣的,大家也都想要及时了解到手机双屏模式如何设置相关信息
    的头像 发表于 04-22 10:30 4.3w次阅读

    三星显示器调模式怎么调?

    通过显示器的MENU按键,调出显示器调节菜单,通过显示器自带的方向按键,调整光标到【图像】-【灵巧模式】中,选择需要的显示模式即可。
    的头像 发表于 04-22 10:51 2.7w次阅读

    为什么串口唤醒STOP模式?如何才能实现串口唤醒STOP模式呢?

    STM32常见的低功耗模式有三种:睡眠模式、STOP模式以及待机模式,STM32L系列还有其他低功耗模式
    的头像 发表于 06-06 11:02 4153次阅读
    为什么<b class='flag-5'>要</b>串口唤醒STOP<b class='flag-5'>模式</b>?如何才能实现串口唤醒STOP<b class='flag-5'>模式</b>呢?

    什么是定长控制功能?变频器如何设置定长控制模式

    什么是定长控制功能?变频器如何设置定长控制模式?变频器定长模式的控制设置是如何工作的? 定长控制功能是变频器控制系统中的一种模式,其主要作用是控制控制中所需轴的行程长度,从而实现工作
    的头像 发表于 10-22 14:38 1572次阅读