【golang/go语言】Go语言代码实践——高复用、易扩展性代码训练

2023-11-10

某个项目里有一段老代码写的不是很好,想着能否通过自己掌握的知识,将其改善一下。感兴趣的小伙伴可以通过了解背景和需求,自己试想下该如何实现,如果有更好的方案也欢迎留言讨论。

1. 背景及需求

(1) 背景

假设我们的下游提供了一个定时任务接口CronJob(cron string, fun func()),它的入参有两个,分别是定时任务执行的频率,以及具体执行哪个函数。我们要借助该定时任务接口实现一个功能:对数据库中同一张表的所有数据的两个不同的字段完成赋值。为了给每个字段赋值,会分别设置两个定时任务,一个是正常的任务处理,5分钟执行一次,负责捞取最近30分钟的数据并完成赋值。另一个是补偿的任务处理,1小时执行一次,负责捞取最近24小时的数据并完成赋值。

(2) 需求

代码的实现要满足以下要求:

  • 因为都是捞取同一张表的数据并完成赋值,所以该部分代码应该复用同一份代码,防止出错;
  • 后续可能有新增字段需要类似的赋值逻辑,所以代码应该有良好的可扩展性;
  • 由于只有字段赋值逻辑不同,所以每次新增字段赋值需求时,代码的改动应该尽可能小。

2. 需求分析及实现

(1) 定时任务代码

由于我们依赖下游的定时任务接口,所以首先给出模拟的定时任务接口。

// CronJob 模拟定时任务,参数分别为调度时间和调度内容
func CronJob(cron string, fun func()) {
	fmt.Println("调度时间: ", cron)
	fmt.Println("定时任务执行开始。。。")
	fun()
	fmt.Println("定时任务执行结束。。。\n\n")
}

(2) 定义接口

我们需要实现的是对字段的赋值方法,由于同一字段的赋值,分为了正常的任务处理和补偿的任务处理两个,所以我们可以定义两个接口方法CommonFix

// ProcessJob 处理任务接口,定义了两个方法
type ProcessJob interface {
	Common(name string) func() // 正常的任务处理
	Fix(name string) func()    // 补偿的任务处理
}

(3) 接口的实现类

定义了接口后,接口的实现类应该分别实现上述两个方法,完成两个字段的赋值逻辑。而从数据库捞取数据的部分显然是通用的,该部分可以独立成为一个函数,该函数可以捞取数据,并将数据的主键id传参给实现类的两个方法,完成该条数据的赋值。

1) 通用部分

正常和补偿任务处理中通用的部分:

// Common 正常的任务处理中通用的部分
func Common(name string, fun func(params string)) func() {
  id := "$模拟id$"
	return func() {
		fmt.Println("任务名称为:", name)
		fmt.Println("正常任务处理的前置操作。。。")
		fun(id)
		fmt.Println("正常任务处理的后置操作。。。")
	}
}

// Fix 补偿的任务处理中通用的部分
func Fix(name string, fun func(params string)) func() {
  id := "$模拟id$"
	return func() {
		fmt.Println("任务名称为:", name)
		fmt.Println("补偿任务处理的前置操作。。。")
		fun(id)
		fmt.Println("补偿任务处理的后置操作。。。")
	}
}

注意下通用方法的入参,这里分别用来接收实现类方法传入的参数,以及字段赋值逻辑的函数。

2) 第一个接口实现类

第一个处理任务,为第一个字段赋值:

// ProcessJob1 第一个处理任务,为第一个字段赋值
type ProcessJob1 struct{}

// Common 第一个处理任务的正常任务处理
func (pj ProcessJob1) Common(name string) func() {
	return Common(name, func(id string) {
		fmt.Printf("常规任务处理中,为id为 %v 的第一个字段进行赋值", id)
	})
}

// Fix 第一个处理任务的补偿任务处理
func (pj ProcessJob1) Fix(name string) func() {
	return Fix(name, func(id string) {
		fmt.Printf("补偿任务处理中,为id为 %v 的第一个字段进行赋值", id)
	})
}

这里需要注意下,由于定时任务接口只接受类型为func()的函数,所以接口实现类的CommonFix方法的返回值都是func()类型。而字段赋值逻辑函数的入参为从通用函数那传入的参数,即数据的主键id。

3) 第二个接口实现类

第二个处理任务,为第二个字段赋值:

// ProcessJob2 第二个处理任务,为第二个字段赋值
type ProcessJob2 struct{}

// Common 第二个处理任务的正常任务处理
func (pj ProcessJob2) Common(name string) func() {
	return Common(name, func(id string) {
		fmt.Printf("常规任务处理中,为id为 %v 的第二个字段进行赋值", id)
	})
}

// Fix 第二个处理任务的补偿任务处理
func (pj ProcessJob2) Fix(name string) func() {
	return Fix(name, func(id string) {
		fmt.Printf("补偿任务处理中,为id为 %v 的第二个字段进行赋值", id)
	})
}

(4) 实现类再封装

由于ProcessJob1和ProcessJob2都可以看做是ProcessJob类型,所以可以为实现类再封装一层,以便统一进行处理。通过分析需求可以知道,实现类的属性可以有任务名称、正常任务处理的调度时间、补偿任务处理的调度时间等组成,所以可以定义以下类型:

// JobConf 定时任务配置
type JobConf struct {
	Name       string     // 定时任务的名称
	CommonCron string     // 正常任务处理的调度时间
	FixCron    string     // 补偿任务处理的调度时间
	ProcessJob ProcessJob // 处理任务对象
}

(5) 添加/获取任务配置

定义了统一的类型之后,就可以创建一个该类型的列表,如果有新增的字段赋值需求,那么就可以很方便的进行扩展了,只需要在列表中新增一个任务配置即可。

// GetJobConfMap 获取所有的任务配置信息
func GetJobConfMap() map[string]JobConf {
	// 定义一个任务配置的列表,如有新增的任务,直接append一个新任务即可
	jobConfList := make([]JobConf, 0)
	jobConfList = append(jobConfList, JobConf{
		Name:       "job1",
		CommonCron: "1 * * * * *",
		FixCron:    "11 * * * * *",
		ProcessJob: ProcessJob1{},
	})
	jobConfList = append(jobConfList, JobConf{
		Name:       "job2",
		CommonCron: "2 * * * * *",
		FixCron:    "22 * * * * *",
		ProcessJob: ProcessJob2{},
	})

	// 获取任务名到任务配置的map
	jobConfMap := make(map[string]JobConf)
	for _, jobConf := range jobConfList {
		jobConfMap[jobConf.Name] = jobConf
	}
	return jobConfMap
}

(6) 主函数

最后就是根据每个任务的配置,启动对应的定时任务了。

func main() {
	// 获取所有的任务配置信息
	jobConfMap := GetJobConfMap()

	// 为每个任务独立设置调度时间,以及处理内容
	for jobName, jobConf := range jobConfMap {
		processJob := jobConf.ProcessJob
		CronJob(jobConf.CommonCron, processJob.Common(jobName))
		CronJob(jobConf.FixCron, processJob.Fix(jobName))
	}
}

3. 完整代码

package main

import (
	"fmt"
)

// CronJob 模拟定时任务,参数分别为调度时间和调度内容
func CronJob(cron string, fun func()) {
	fmt.Println("调度时间: ", cron)
	fmt.Println("定时任务执行开始。。。")
	fun()
	fmt.Println("定时任务执行结束。。。\n\n")
}

// ProcessJob 处理任务接口,定义了两个方法
type ProcessJob interface {
	Common(name string) func() // 正常的任务处理
	Fix(name string) func()    // 补偿的任务处理
}

// Common 正常的任务处理中通用的部分
func Common(name string, fun func(params string)) func() {
  id := "$模拟id$"
	return func() {
		fmt.Println("任务名称为:", name)
		fmt.Println("正常任务处理的前置操作。。。")
		fun(id)
		fmt.Println("正常任务处理的后置操作。。。")
	}
}

// Fix 补偿的任务处理中通用的部分
func Fix(name string, fun func(params string)) func() {
  id := "$模拟id$"
	return func() {
		fmt.Println("任务名称为:", name)
		fmt.Println("补偿任务处理的前置操作。。。")
		fun(id)
		fmt.Println("补偿任务处理的后置操作。。。")
	}
}

// JobConf 定时任务配置
type JobConf struct {
	Name       string     // 定时任务的名称
	CommonCron string     // 正常任务处理的调度时间
	FixCron    string     // 补偿任务处理的调度时间
	ProcessJob ProcessJob // 处理任务对象
}

// ProcessJob1 第一个处理任务,为第一个字段赋值
type ProcessJob1 struct{}

// Common 第一个处理任务的正常任务处理
func (pj ProcessJob1) Common(name string) func() {
	return Common(name, func(id string) {
		fmt.Printf("常规任务处理中,为id为 %v 的第一个字段进行赋值", id)
	})
}

// Fix 第一个处理任务的补偿任务处理
func (pj ProcessJob1) Fix(name string) func() {
	return Fix(name, func(id string) {
		fmt.Printf("补偿任务处理中,为id为 %v 的第一个字段进行赋值", id)
	})
}

// ProcessJob2 第二个处理任务,为第二个字段赋值
type ProcessJob2 struct{}

// Common 第二个处理任务的正常任务处理
func (pj ProcessJob2) Common(name string) func() {
	return Common(name, func(id string) {
		fmt.Printf("常规任务处理中,为id为 %v 的第二个字段进行赋值", id)
	})
}

// Fix 第二个处理任务的补偿任务处理
func (pj ProcessJob2) Fix(name string) func() {
	return Fix(name, func(id string) {
		fmt.Printf("补偿任务处理中,为id为 %v 的第二个字段进行赋值", id)
	})
}

// GetJobConfMap 获取所有的任务配置信息
func GetJobConfMap() map[string]JobConf {
	// 定义一个任务配置的列表,如有新增的任务,直接append一个新任务即可
	jobConfList := make([]JobConf, 0)
	jobConfList = append(jobConfList, JobConf{
		Name:       "job1",
		CommonCron: "1 * * * * *",
		FixCron:    "11 * * * * *",
		ProcessJob: ProcessJob1{},
	})
	jobConfList = append(jobConfList, JobConf{
		Name:       "job2",
		CommonCron: "2 * * * * *",
		FixCron:    "22 * * * * *",
		ProcessJob: ProcessJob2{},
	})

	// 获取任务名到任务配置的map
	jobConfMap := make(map[string]JobConf)
	for _, jobConf := range jobConfList {
		jobConfMap[jobConf.Name] = jobConf
	}
	return jobConfMap
}

func main() {
	// 获取所有的任务配置信息
	jobConfMap := GetJobConfMap()

	// 为每个任务独立设置调度时间,以及处理内容
	for jobName, jobConf := range jobConfMap {
		processJob := jobConf.ProcessJob
		CronJob(jobConf.CommonCron, processJob.Common(jobName))
		CronJob(jobConf.FixCron, processJob.Fix(jobName))
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【golang/go语言】Go语言代码实践——高复用、易扩展性代码训练 的相关文章

随机推荐

  • 同步服务器安装系统,时间同步服务器的配置方法

    知道什么是时间同步服务器的配置方法吗 下面是学习啦小编跟大家分享的是时间同步服务器的配置方法 欢迎大家来阅读学习 时间同步服务器的配置方法 方法 步骤 双击任务栏右下角 时间 打开 时间和日期 属性 设置对话框 2选择 Internet时间
  • SimpleDateFormat用法详解

    SimpleDateFormat类是一个以语言环境敏感的方式来格式化和解析日期的工具类 它允许你将日期格式化为字符串 或从字符串解析为日期 格式化日期为字符串 SimpleDateFormat sdf new SimpleDateForma
  • 在linux下编译多线程需要如下设置

    编译时这样输入命令 gcc xxx c o xxx out lpthread
  • LeetCode知识点总结 - 1710

    LeetCode 1710 Maximum Units on a Truck 考点 难度 Sorting Easy 题目 You are assigned to put some amount of boxes onto one truck
  • Xilinx 7系FPGA LVDS使用要注意了,供电不能搞错

    最近新做了一块板子 用到Spartan 7芯片对前级视频源叠加OSD菜单 前级会将HMDI转成LVDS送给FPGA处理 在原理图设计阶段没有仔细阅读fpga手册 导致LVDS BANK供电错误 应该接2 5V 实际接3 3V 且BANK供电
  • 射频与无线技术入门 读书记录

    一 基础概念 无线系统框图 瓦特W 功率测量单位 能量 功率 时间 如100W的灯泡亮了2小时 能量就是100w 2 就是200W H的能量 波段 使用字母表示一定范围的频率 载波 载波只能使用模拟信号 在这个模拟信号上承载模拟或者数字信息
  • 跨域的解决方案

    一 跨域 1 概念 指的是浏览器不能执行其他网站的脚本 它是由浏览器的同源策略造成的 是 浏览器对javascript施加的安全限制 2 同源策略 是指协议 域名 端口都要相同 其中有一个不同都会产生跨域 3 跨域流程 二 解决跨域方案 1
  • [转载] 陈皓——程序员技术练级攻略

    PS 原文出自酷壳上的陈皓对程序员从入门到精通的攻略 让你感受一下真正的大神吧 又是阿里人 他的文章真心不错 希望对你也有用 原文地址 http coolshell cn articles 4990 html 陈皓酷壳博客地址 http c
  • oracle failover mode,Oracle RAC FailOver配置

    Oracle RAC FailOver配置 Oracle RAC主要为数据库的应用提供了HA High Available 的环境 HA体现在负载均衡 loadbalance 和容错 failover 两个方面 Oracle RAC 的Fa
  • 机器学习---期望+方差+标准差+协方差

    1 期望 在概率论和统计学中 数学期望 mathematic expectation 或均值 亦简称期望 是试验中每次可能结果的概率乘以其结果的总和 是最基本的数学特征之一 它反映随机变量平均取值的大小 大数定律表明 随着重复次数接近无穷大
  • Optimal Coin Change(完全背包计数)

    题目描述 In a 10 dollar shop everything is worthy 10 dollars or less In order to serve customers more effectively at the cas
  • Java对象序列化

    Java 对象序列化 对象序列化的目标是将对象保存到磁盘中 或允许在网络中直接传输对象 对象序列化机制允许把内存中的 java 对象转换成为与平台无关的二进制流 从而允许把这种二进制流持久保存到磁盘上 实现对象序列化 该类实现接口 seri
  • texstudio与ctex_Latex的使用(Ctex+TeXstudio)

    1 下载 CTEX Latex 本来是只支持英文的 但是实在太好用了 遂结合中国的团队以及有识之士 开发了这个 CTEX CTEX 有 TexLive TexLive 为 Latex 安装包的名字 的所有内容 还包括了中文的支持 所以这里我
  • 【C++】详解inline

    2023年8月28日 周一晚上 目录 优点 缺点 使用条件 为什么调用函数会有开销 举例说明 优点 当使用inline关键字声明一个函数时 编译器会将函数体内联到所有调用该函数的地方 这可以提高执行效率 因为无需进行函数调用的开销 缺点 但
  • android 日期控件

    相关布局文件
  • android:OKHttp的使用

    1 之前学习了两种基于http访问服务器的方法 一种是HttpURLConenction 一种是Apache下的HttpClient 说实话 这两种方法操作起来都不是很简单明了 所以当前首选的网络通信库是由Square公司开发的OKHttp
  • 有关C++,Qt中使用指针的注意事项

    1 指针一般在创建的时候都应该初始化 除非你能保证要么你不会用到这个指针 要么在你使用之前它以及被被初始化了 如果不初始化 它就是野指针 在Debug模式下 VC 编译器会把未初始化的栈内存上的指针全部填成 0xcccccccc 当字符串看
  • RUNOOB python练习题6 斐波那契数列

    用来练手的python 练习题其六 原链接 python练习实例6 题干 斐波那契数列 斐波那契数列可以说是很好的递归理解工具了 这里就用递归实现一下斐波那契数列 源代码如下 返回fibonacci数列中某一项的数值 def Fibonac
  • 【面试题】2023年最新前端面试题-react篇

    原文见 语雀 https www yuque com deepstates interview hia3k3 核心概念 元素渲染 组件 props state refs 使用场景 如何创建 如何访问 组件通信 父子 祖孙 兄弟组件通信 生命
  • 【golang/go语言】Go语言代码实践——高复用、易扩展性代码训练

    某个项目里有一段老代码写的不是很好 想着能否通过自己掌握的知识 将其改善一下 感兴趣的小伙伴可以通过了解背景和需求 自己试想下该如何实现 如果有更好的方案也欢迎留言讨论 1 背景及需求 1 背景 假设我们的下游提供了一个定时任务接口Cron