恩施网站建设公司,wordpress 注册 验证码,罗湖区网站建设多少钱,gae建立wordpress博客文章目录 生成器模式生成器代码生成 本文用生成器模式作为例子#xff0c;来演示如何用代码生成代码。
生成器模式
熟悉 Java 开发的同学都知道#xff0c;lombok 有一个著名的注解 Builder #xff0c;只要加在类上面#xff0c;就可以自动生成 Builder 模式的代码。如下… 文章目录 生成器模式生成器代码生成 本文用生成器模式作为例子来演示如何用代码生成代码。
生成器模式
熟悉 Java 开发的同学都知道lombok 有一个著名的注解 Builder 只要加在类上面就可以自动生成 Builder 模式的代码。如下所示
Builder
public class DetectionQuery {private String uniqueKey;private Long startTime;private Long endTime;
}然后就可以这样使用
return DetectionQuery.builder().uniqueKey(uniqueKey).startTime(startTime).endTime(endTime).build();是不是很爽
不过 Go 可没有这样好用的注解。Go 你得自己手写。假设我们要造一辆车车有车身、引擎、座位、轮子。Go 的生成器模式的代码是这样子的
package modelimport fmttype ChinaCar struct {Body stringEngine stringSeats []stringWheels []string
}func newChinaCar(body string, engine string, seats []string, wheels []string) *ChinaCar {return ChinaCar{Body: body,Engine: engine,Seats: seats,Wheels: wheels,}
}type CarBuilder struct {body stringengine stringseats []stringwheels []string
}func ChinaCharBuilder() *CarBuilder {return CarBuilder{}
}func (b *CarBuilder) Build() *ChinaCar {return newChinaCar(b.body, b.engine, b.seats, b.wheels)
}func (b *CarBuilder) Body(body string) *CarBuilder {b.body bodyreturn b
}func (b *CarBuilder) Engine(engine string) *CarBuilder {b.engine enginereturn b
}func (b *CarBuilder) Seats(seats []string) *CarBuilder {b.seats seatsreturn b
}func (b *CarBuilder) Wheels(wheels []string) *CarBuilder {b.wheels wheelsreturn b
}func main() {car : ChinaCharBuilder().Body(More advanced).Engine(Progressed).Seats([]string{good, nice}).Wheels([]string{solid, smooth}).Build()fmt.Printf(%v, car)
}生成器模式怎么写遵循三步即可
1 先构造一个对应的生成器这个生成器与目标对象有一样的属性
2 对于每一个属性有一个方法设置属性然后返回生成器的引用本身
3 最后调用生成器的 Build 方法这个方法会调用目标对象的构造器来生成目标对象。
为啥不直接调用目标对象的构造器要这么拐弯抹角呢因为生成器模式一般用于复杂对象的构造。这个复杂对象的每一个组件都需要逐步构造比如每一个Set方法中可能都有一些对于属性的校验或者其他业务逻辑而不是简单的给属性赋值就行。必须等所有组件都正确构造完成后才能返回一个可用的目标对象。像 CarBuilder 这种才算是生成器模式的合理使用。而 DetectionQuery 的 builder 模式只是为了享受链式调用的畅感。
生成器代码生成
用代码生成代码嗯其实不算稀奇。代码也只是一种普通的可读文本而已。
模板是用于动态生成文本的常用技术。虽然看上去不算特别高明的方式但也很管用。咱们使用 Go template 来实现它。
思路与实现 首先要分析哪些是固定的文本哪些是动态的文本。
红框框出来的都是动态文本。事实上除了几个关键字和括号是静态的以外其它基本都是动态生成的。这些文本通常是根据业务对象类型和业务对象的属性名及属性类型来推理出来的。
先根据最终要生成的代码把模板文件给定义出来这里可以用自然语言先填充再替换成技术实现
func New{{ 目标对象类型 }}(逗号分隔的属性名 属性类型列表)) *{{ 目标对象类型 }} {return {{ 目标对象类型 }}{每一行都是 属性名 属性名 属性名小写}
}type {{ 生成器类型 }} struct {每一行都是 属性名 属性类型属性名小写
}func {{ 生成器方法名 }}() *{{ 生成器类型 }} {return {{ 生成器类型 }}{}
}func (b *{{ 生成器类型 }}) Build() *{{ 目标对象类型 }} {return New{{ 目标对象类型 }}(逗号分隔的 b.属性名 列表
}// 对于每一个属性遍历做如下动作func (b *{{ 生成器类型 }}) {{ 属性名 }}({{ 属性名小写 }} {{ 属性类型 }}) *{{ 生成器类型 }} {b.{{ 属性名小写 }} {{ 属性名小写 }}return b
}然后抽象出用来填充动态文本的填充对象即要传给模版的参数对象存储了在模板中需要用的动态信息
type BuilderInfo struct {BuilderMethod stringBuilderClass stringBizClass stringAttrs []Attr
}type Attr struct {Name stringType string
}func newAttr(Name, Type string) Attr {return Attr{Name: Name, Type: Type}
}接下来要根据具体的模板语言来填充上面的自然语言同时从目标对象中生成填充对象来填充这些动态文本和自定义函数。
如下代码所示
builder_tpl 就是生成器模式的代码模板文本。我们先用具体的值填充把模板调通然后再把这些具体的值用函数替换。
func LowercaseFirst(s string) string {r, n : utf8.DecodeRuneInString(s)return string(unicode.ToLower(r)) s[n:]
}func MapName(attrs []Attr) []string {return util.Map[Attr, string](attrs, func(attr Attr Attr, string) string { return b. LowercaseFirst(attr.Name) })
}func MapNameAndType(attrs []Attr) []string {return util.Map[Attr, string](attrs, func(attr Attr Attr, string) string { return LowercaseFirst(attr.Name) LowercaseFirst(attr.Type) })
}func autoGenBuilder(builder_tpl string) {t1 : template.Must(template.New(test).Funcs(template.FuncMap{lowercaseFirst: LowercaseFirst, join: strings.Join, mapName: MapName, mapNameAndType: MapNameAndType,}).Parse(builder_tpl))bi : BuilderInfo{BuilderMethod: QueryBuilder,BuilderClass: CarBuilder,BizClass: ChinaCar,Attrs: []Attr{newAttr(Body, string), newAttr(Engine, string),newAttr(Seats, []string), newAttr(Wheels, []string)},}t1.ExecuteTemplate(os.Stdout, test, bi)
}func main() {builder_tpl : func New{{ .BizClass }}({{- join (mapNameAndType .Attrs) , }})) *{{ .BizClass }} {return {{ .BizClass }}{{{ range .Attrs }}{{ .Name }}: {{ lowercaseFirst .Name }},{{ end }}}
}type {{ .BuilderClass }} struct {{{ range .Attrs }}{{ lowercaseFirst .Name }} {{ .Type }}
{{ end }}
}func {{ .BuilderMethod }}() *{{ .BuilderClass }} {return {{ .BuilderClass }}{}
}func (b *{{ .BuilderClass }}) Build() *{{ .BizClass }} {return New{{ .BizClass }}({{- join (mapName .Attrs) , }})
}{{- range .Attrs }}
func (b *{{ $.BuilderClass }}) {{ .Name }}({{ lowercaseFirst .Name }} {{ .Type }}) *{{ $.BuilderClass }} {b.{{ lowercaseFirst .Name }} {{ lowercaseFirst .Name }}return b
}
{{- end }}car : model.ChinaCar{}//autoGenBuilder(builder_tpl)autoGenBuilder2(builder_tpl, car)
}这里基本上概括了Go template 的常用语法
{{ . }} 表示顶层作用域对象也就是你从如下方法传入的 bi 对象。 {{ .BuilderClass }} 就是取bi.BuilderClass , {{ .Attrs }} 就是取 bi.Attrs
t1.ExecuteTemplate(os.Stdout, test, bi)这有个 range 循环 取 Attrs 里的每一个元素进行循环。注意到range 里面的 {{ .Name }} 的.表示的是Attrs里的每一个元素对象。 {{ range .Attrs }}{{ .Name }}: {{ lowercaseFirst .Name }},{{ end }}这里还传入了一个自定义函数 lowercaseFirst 可以通过如下方法传入
t1 : template.Must(template.New(test).Funcs(template.FuncMap{lowercaseFirst: LowercaseFirst, join: strings.Join, mapName: MapName, mapNameAndType: MapNameAndType,}).Parse(builder_tpl))还有一个技巧就是如何在 range 循环里引用顶层对象。这里要引用 BuilderClass 的值必须用 $.BuilderClass否则输出为空。
{{- range .Attrs }}
func (b *{{ $.BuilderClass }}) {{ .Name }}({{ lowercaseFirst .Name }} {{ .Type }}) *{{ $.BuilderClass }} {b.{{ lowercaseFirst .Name }} {{ lowercaseFirst .Name }}return b
}
{{- end }}嗯多写写就熟了。通过实战来练习和掌握是一种高效学习之法。
注意一定要写 . 号。我最开始老是忘记写。然后就卡住没响应了。
go template 报错不太友好。分三种情况
直接卡住你也不知道到底发生了什么。比如 {{ .BuilderClass }} 写成 {{ BuilderClass }}直接报错地址引用错误。比如模板语法错误。不输出内容。比如引用不到内容。
进一步完善 接下来就要把写死的 BuilderMethod, BuilderClass, BizClass 和Attrs通过给定的业务类型来生成。
func GetBizClass(t any) string {qualifiedClass : fmt.Sprintf(%T, t)return qualifiedClass[strings.Index(qualifiedClass, .)1:]
}func GetAttributes(obj any) []Attr {typ : reflect.TypeOf(obj)attrs : make([]Attr, typ.NumField())for i : 0; i typ.NumField(); i {field : typ.Field(i)attr : Attr{Name: field.Name,Type: field.Type.String(),}attrs[i] attr}return attrs
}用 GetBizClass 和 GetAttributes 生成的值分别填充那几处硬写的值即可。
程序的主体本文已经都给出来了读者也可以将其拼接起来做一次完型填空。