124. Go Template应用实例:用代码生成代码
文章目录
- 生成器模式
- 生成器代码生成
本文用生成器模式作为例子,来演示如何用代码生成代码。
生成器模式
熟悉 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 "fmt"type 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 生成的值分别填充那几处硬写的值即可。
程序的主体,本文已经都给出来了,读者也可以将其拼接起来,做一次完型填空。
相关文章:
124. Go Template应用实例:用代码生成代码
文章目录 生成器模式生成器代码生成 本文用生成器模式作为例子,来演示如何用代码生成代码。 生成器模式 熟悉 Java 开发的同学都知道,lombok 有一个著名的注解 Builder ,只要加在类上面,就可以自动生成 Builder 模式的代码。如下…...
【AI实践】阿里云方言文本转语音TTS
最近要做一些普通话和方言demo 找一个免费工具 免费在线文字转语音工具 | edge-tts 在线体验 (bingal.com) 还有一些方言在阿里云上找了下,基于官方demo改了一下 阿里云语音合成接口说明_智能语音交互(ISI)-阿里云帮助中心 (aliyun.com) 如何下载安装、使用语音…...
java 之 各类日期格式转换
一、前言 大家在开发过程中必不可少得和日期打交道,对接别的系统时,时间日期格式不一致,每次都要转换! 从 Java1 到 Java8 将近 20 年,再加上 Java8 的普及时间、各种历史 API 兼容过渡时间。我们很多时候需要在旧时间 API 与新时…...
Nvidia黄仁勋对话Meta扎克伯格:AI和下一代计算平台的未来 | SIGGRAPH 2024对谈回顾
在今年的SIGGRAPH图形大会上,Nvidia创始人兼CEO黄仁勋与Meta创始人马克扎克伯格进行了一场长达60分钟的对谈。这场对话不仅讨论了AI的未来发展和Meta的开源哲学,还发布了不少新产品,并深入探讨了下一代计算平台的可能性。 引言 人工智能的发…...
【JAVA设计模式】适配器模式——类适配器模式详解与案例分析
前言 在软件设计中,适配器模式(Adapter Pattern)是一种结构型设计模式,旨在使不兼容的接口能够协同工作。它通过引入一个适配器类,帮助两个接口之间进行适配,使得它们能够互相操作。本文将详细介绍适配器模…...
【Vue】全局组件和局部组件
一、全局组件 定义: 全局组件是在整个Vue应用中都可以使用的组件。它们被注册在Vue的根实例上,因此可以在任何子组件的模板中被引用,而无需在每个组件中重复注册。 注册方式: 全局组件通过Vue.component方法进行注册。这个方法接…...
react引入高德地图并初始化卫星地图
react引入高德地图并初始化卫星地图 1.安装依赖 yarn add react-amap amap/amap-jsapi-loader2.初始化地图 import AMapLoader from "amap/amap-jsapi-loader"; import { FC, useEffect, useRef, useState } from "react";const HomeRight () > {con…...
2024最简七步完成 将本地项目提交到github仓库方法
2024最简七步完成 将本地项目提交到github仓库方法 文章目录 2024最简七步完成 将本地项目提交到github仓库方法一、前言二、具体步骤1、github仓库创建2、将远程仓库拉取并合并(1)初始化本地仓库(2)本地仓库与Github仓库关联&…...
前端WebSocket入门,看这篇就够啦!!
在HTML5 的早期开发过程中,由于意识到现有的 HTTP 协议在实时通信方面的不足,开发者开始探索能够在 Web 环境下实现双向实时通信的新的通信协议,提出了 WebSocket 协议的概念。 一、什么是 WebSocket? WebSocket 是一种在单个 T…...
漏洞复现-F6-11泛微-E-Cology-SQL
本文来自无问社区,更多漏洞信息可前往查看http://www.wwlib.cn/index.php/artread/artid/15575.html 0x01 产品简介 泛微协同管理应用平台e-cology是一套企业级大型协同管理平台 0x02 漏洞概述 该漏洞是由于泛微e-cology未对用户的输入进行有效的过滤࿰…...
Turbo Boost 禁用
最近在做OAI NR的时候关闭CPU 睿频的时候出了一些问题,这里我把我找到的资料记录一下: 禁用 Turbo Boost 的过程可能会因不同的 BIOS/UEFI 和操作系统设置而有所不同。以下是一些可能的原因及解决方法: 可能的原因 BIOS/UEFI 设置问题: 你的…...
假期BUUCTF小练习3
文章目录 [极客大挑战 2019]BuyFlag[BJDCTF2020]Easy MD5[HCTF 2018]admin第一种方法 直接登录第二种方法 flack session伪造第三种方法Unicode欺骗 [MRCTF2020]你传你🐎呢[护网杯 2018]easy_tornadoSSTI注入 [ZJCTF 2019]NiZhuanSiWei [极客大挑战 2019]BuyFlag 一…...
【ubuntu系统】在虚拟机内安装Ubuntu
Ubuntu系统装机 描述新装机后的常规配置, 虚拟机使用vbox terminal 打不开 CTRL ALT F3 进入命令行模式(需要返回桌面时CTRL ALT F1)root用户登入cd /etc/default vi locale LANG“en_US” 改成 LANG“en_US.UTF-8”保存修改后&…...
Python初学者必须掌握的基础知识点
Python初学者必须掌握的基础知识点包括数据类型与变量、控制结构(条件语句和循环语句)、基本数据结构(列表、元组、字典、集合)、函数与模块、以及字符串处理等。以下是对这些基础知识点及其对应代码的详细介绍: 1. …...
ESP32是什么?
ESP32是一款由乐鑫信息科技(Espressif Systems)推出的高度集成的低功耗系统级芯片(SoC),它结合了双核处理器、无线通信、低功耗特性和丰富的外设,特别适用于各种物联网(IoT)应用。以…...
jemalloc分析内存
分析内存泄漏过程中, 由于tcmalloc不能长时间开启heap profile(会不停涨内存,导致内存爆掉).尝试换jemalloc. 交叉编译: git clone https://github.com/jemalloc/jemalloc.git./autogen.sh./configure --hostaarch64-…...
【QT】qss
目录 基本语法 设置全局样式 问题 分离样式代码 方案1 方案2 选择器 概况 子控件选择器 伪类选择器 盒子模型 修改控件样式示例 按钮 属性小结 复选框 属性小结 输入框 属性小结 列表框 属性小结 渐变色 示例: 菜单栏 设置菜单栏的背景…...
Java处理大数据的技巧
大数据处理是现代计算机科学中的一个重要领域,通过高效的算法和工具,我们可以从大量数据中提取有价值的信息。本文将介绍一些处理大数据的技巧和策略,并讨论如何通过Java与MySQL实现高效的大数据处理。 一、什么是大数据处理? 大…...
JavaScript基础——JavaScript常见语句(判断语句、循环语句、控制流语句)
JavaScript提供了丰富的语句来控制程序的执行流程,包括用于条件判断的if、switch和三元运算符,以及用于循环的for、while、do...while、for...in和for...of。此外,还有控制流语句如break、continue和return。 判断语句 if 语句 if 语句&…...
材质球向shader传值失败
unity中导入spine模型,当模型挂载SkeletonMecanim组件后,发现材质球向shader传值失败,改为SetPropertyBlock后可行。 //spine模型使用材质球传参数,当spine模型上挂载有SkeletonMecanim的情况下,会传值失败!!!!// for…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...
