当前位置: 首页 > 科技观察

活跃在众多Go项目中的一种编程模式

时间:2023-03-17 13:20:22 科技观察

今天我们介绍一种Go语言中非常流行的编程模式:FunctionalOptions。这种模式解决的问题是如何更加动态灵活地为对象配置参数。可能读者还不太明白这个痛点,别着急,下面我们会详细讲解。问题假设我们在代码中定义了一个用户结构体对象User,它有如下属性。typeUserstruct{IDstring//requireditemNamestring//requireditemAgeint//notrequireditemGenderbool//notrequireditem}在初始化这个对象的时候,最简单的方法就是直接填入属性值,例如u:=&User{ID:"12glkui234d",Name:"Chopper",Age:18,Gender:true}但是这里有个问题:并不是User对象中的所有属性都可以导出,比如User有一个属性字段名为password(首字母小写,不导出),如果需要在其他模块中构造User对象,密码字段不能这样填写。所以我们需要定义构造User对象的函数。首先,我们能想到的最简单的构造器方法如下。funcNewUser(id,namestring,ageint,genderbool)*User{return&User{ID:id,Name:name,Age:age,Gender:gender,}}但是有一些问题:对于User对象,只ID和Name属性是必须的,Age和Gender不是必须的,并且不能设置默认值,比如Age的默认值为0,Gender的默认值为false,这显然是不合理的。面对这个问题,我们可以采取哪些解决方案?方案一:多功能构造我们能想到的最粗暴的方案是:为每个参数情况设置一个构造函数。以下代码显示funcNewUser(id,namestring)*User{return&User{ID:id,Name:name}}funcNewUserWithAge(id,namestring,ageint)*User{return&User{ID:id,Name:姓名,年龄:年龄}}funcNewUserWithGender(id,namestring,genderbool)*User{return&User{ID:id,Name:name,Gender:gender}}funcNewUserWithAgeGender(id,namestring,ageint,genderbool)*User{return&User{ID:id,Name:name,Age:age,Gender:gender}}该方法适用于参数较少且不易改变的情况。Go标准库中也使用了该方法,例如net包中的Dial和DialTimeout方法。funcDial(network,addressstring)(Conn,error){}funcDialTimeout(network,addressstring,timeouttime.Duration)(Conn,error){}但是这个方法的缺陷也很明显:试想一下,如果objectUser添加了参数字段Phone,那么我们需要添加多少个组合函数呢?解决方案2:配置另一种常见的方式是配置。我们将所有可选参数放入Config的一个配置结构中。typeUserstruct{IDstringNamestringCfg*Config}typeConfigstruct{AgeintGenderbool}funcNewUser(id,namestring,cfg*Config)*User{返回&User{ID:id,Name:name,Cfg:cfg这样一来,我们只需要一个NewUser()函数,无论后面添加多少配置选项,NewUser函数都不会被销毁。但是,这样的话,我们需要先构造Config对象。这时候Config的构建又回到了第一种方案中的问题。解决方案三:功能选项模式面对这样的问题,我们也可以选择功能选项模式。首先我们定义一个Option函数类型typeOptionfunc(*User)然后定义一个函数func为每个属性值返回一个Option函数funcWithAge(ageint)Option{returnfunc(u*User){u.Age=age}}funcWithGender(genderbool)Option{returnfunc(u*User){u.Gender=gender}}此时我们将User对象的构造函数改成funcNewUser(id,namestring,options...Option)*User{u:=&User{ID:id,Name:name}for_,option:=rangeoptions{option(u)}returnu}根据这个构造方法,我们可以这样配置User对象u:=NewUser("12glkui234d","Chopper",WithAge(18),WithGender(true))之后,无论User中添加什么参数XXX,我们只需要添加对应的WithXXX函数,是不是很优雅的?FunctionalOptions是我们在各种项目中经常可以找到的一种编程模式。比如我在tidb项目中只使用opts...关键字搜索,就可以看到那么多使用FunctionalOptions的代码(截图没有全部包括在内)。总结功能选项模式解决了如何动态灵活地为对象配置参数的问题,但是需要在合适的场景下使用。当对象的配置参数比较复杂时,比如可选参数很多、非导入字段、可能随版本增加的参数等,功能选项模式可以很好地帮助我们。