Go语言实践[回顾]教程08--通过时间判断时辰的示例【下】

发布于:2022-12-09 ⋅ 阅读:(885) ⋅ 点赞:(0)

封装函数向模块化开发方向修改

  在上一节的代码中,已经充分简化了代码量。但是所有的逻辑都放在 main 函数里是不理想的,通常 main 函数内尽量少放代码,只放一些与主流程直接相关的,以使代码逻辑更清晰易读,在项目复杂代码量较大时才更易编写和维护。

  通常使用的方式就是模块化开发(近些年出现很多新名词,其实究其根本还是模块化开发),模块化开发的第一步就是要把有着具体功能且可以独立实现的代码段封装成函数,然后在需要该功能的地方调用该函数即可。那针对上一节代码,修改方向就是要找出可以封装成函数的代码段进行封装。寻找的依据就是这段代码要具有独立且完整的功能实现。

使用单个返回值函数的源代码

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
	"time"
)

// getDoubleHour 根据小时获得时辰名和俗称名
// 参数:
// hour,int 类型,传入 0~23 范围内的小时数字
// 返回值:
// 无名,字符串数组类型,两个元素,依次是时辰名、俗称名
func getDoubleHour(hour int) [2]string {
	name := [12][2]string{
		{"子时", "子夜"}, {"丑时", "鸡鸣"}, {"寅时", "黎明"}, {"卯时", "破晓"},
		{"辰时", "早晨"}, {"巳时", "上午"}, {"午时", "中午"}, {"未时", "日斜"},
		{"申时", "日馎"}, {"酉时", "傍晚"}, {"戌时", "黄昏"}, {"亥时", "深夜"},
	}
	num := 0

	if hour%2 == 0 { // 是偶数
		num = hour / 2
	} else if hour != 23 { // 是奇数且不等于 23
		num = (hour + 1) / 2
	}
	return name[num]
}

// 主函数,程序入口
func main() {
	shiChenArr := getDoubleHour(time.Now().Hour())
	fmt.Println("现在是" + shiChenArr[0] + ",俗称" + shiChenArr[1])
}

  上述代码编译运行,输出结果是正确的。

  经过分析,通过小时数字来得出应属于的时辰,这部分为一个完整并可独立出来的功能。那么就将上一节中有关代码提取出来,封装成一个函数。

  第9~13行,是对第14行声明的函数的注释说明,建议创建函数一定要写注释,这样代码才易懂好维护,不会因时间推移而一时看不懂自己的代码,浪费很多时间精力。注释,是个好习惯,也是编程规范中很重要的部分。

  第14~28行就是完成上述功能的一个完整的函数。先看第14行:
  func 是声明函数的关键字。
  getDoubleHour 是自定义的函数名,意思是获取时辰。
  (hour int) 括号是放置函数的输入参数的,这里有一个参数 hour ,是 int 类型,用于调用时输入小时数。不传入小时数我们就无判断依据了,所以是必传的。hour 在函数体内同局部变量一样存在。如果不需要传入参数的函数,这里就是一个空括号,如 main 函数。
  [2]string 是这个函数的返回值数据类型。我们需要获取时辰名及俗称名,这两个在一个子数组中,所以可以整体以数组形式返回,因此返回值这里声明为有两个字符串元素的数组类型。如果不需要返回值的函数,就没有这部分声明,如 main 函数。

  第15~26行,是完全可以使用上一节最后的源代码中的第10~27行代码,只删除第11行即可。这里是为了大家熟悉Go 语言的各种语法特点,有意的做了调整,这个调整不是封装函数所必需的。

  第15~20行,与上一节在 main 函数里声明的局部变量 name 和 num 基本相同,只是声明方式使用了简化模式。对 num 也给了初始值 0,这个初始值后面会用到。

  第22~26行,这段代码较上一节有适当修改。是借用 num 有初始值 0 的原理,就可以将原来 小时等于23 的那个判断去掉了,因为那个判断成立也是给 num 赋 0 值。这样我们就只做除了 小时等于23 之外的判断即可。下面就完整的描述一下这个判断逻辑:
  hour 是传入的小时数,首先判断它是偶数吗?
  如果是偶数(那肯定不会等于 23,所以不考虑 23 的影响),那就直接将 hour 除以 2 的结果赋值给 num。
  如果不是偶数,那一定是奇数,所以要继续判断一下是否不等于 23(因为23是奇数,且等于23要排除,所以用不等于判断),如果确实是不等于 23,那就将 hour 加 1 后的结果再除以 2 的最终结果赋值给 num。
  那剩下的就只有 hour 等于 23 这一种情况了,判断逻辑里没有这段代码了。原因是不需要处理了,因为 num 本来在初始化的时候就是等于 0 了。你要加上这段逻辑处理,也还是让 num 等于 0,就多此一举了。因此这个判断分支就被省略了。

  第27行,return 是结束当前代码块(这里是函数)的关键字,如果后面不跟任何值或变量,则结束函数运行,不会返回任何内容(在未声明命名返回值的情况下)。这里后面跟了 name[num],意思就是要把数组 name 的 num 号索引的元素返回输送到调用这个函数的位置(也就是第32行等号的右侧)。name[num] 得到的实际值也是数组,就是 name 数组的第二维中的某一个子数组,所以第14行的返回值声明才用了 [2]string 类型。

  第32行,声明一个局部变量 shiChenArr,初始化值则是调用上面声明定义的函数 getDoubleHour 的返回值,而函数需要传入小时数。我们需求里要求的是取当前系统的时间,那我们就可以把获取当前系统时间的小时部分的语句 time.Now().Hour() 直接放到 getDoubleHour 的参数位置,也就是后面的括号内。经过这样调用,time.Now().Hour() 返回的当前小时数就等于赋值给了 getDoubleHour 函数的参数 hour ,并执行函数体的代码。函数执行到最后,返回了一个数组,赋值给了局部变量 shiChenArr,这个变量里就有了时辰名、俗称名两个元素。

  第33行,shiChenArr[0] 是取的返回的数组中的第1个元素,也就是时辰名。shiChenArr[1] 是取的返回的数组中的第2个元素,也就是俗称名。

使用多个返回值函数的源代码

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
	"time"
)

// getDoubleHour 根据小时获得时辰名和俗称名
// 参数:
// hour,int 类型,传入 0~23 范围内的小时数字
// 返回值:
// 两个无名,字符串类型,依次是时辰名、俗称名
func getDoubleHour(hour int) (string, string) {
	name := [12][2]string{
		{"子时", "子夜"}, {"丑时", "鸡鸣"}, {"寅时", "黎明"}, {"卯时", "破晓"},
		{"辰时", "早晨"}, {"巳时", "上午"}, {"午时", "中午"}, {"未时", "日斜"},
		{"申时", "日馎"}, {"酉时", "傍晚"}, {"戌时", "黄昏"}, {"亥时", "深夜"},
	}
	num := 0

	if hour%2 == 0 { // 是偶数
		num = hour / 2
	} else if hour != 23 { // 是奇数且不等于 23
		num = (hour + 1) / 2
	}
	return name[num][0], name[num][1]
}

// 主函数,程序入口
func main() {
	shiChen, suCheng := getDoubleHour(time.Now().Hour())
	fmt.Println("现在是" + shiChen + ",俗称" + suCheng)
}

  上述代码编译运行,输出结果是正确的。

  这次修改是针对函数声明结构的调整,其他逻辑没有变,所以只说一下与改变相关的部分。

  第14行,返回值声明部分变成了 (string, string),这表示有两个返回值,都是字符串类型,两个的区分是依据先后顺序的位置。Go 语言的函数支持多个返回值,声明多个返回值时,返回值部分的声明要用括号包起来,各返回值定义之间用逗号分隔。

  第27行,因为声明的是有两个返回值,那在 return 的后面也要有两个值,也是要用逗号分隔。这里返回的是已经在 name 数组取到第二维元素的两个字符串,依次是时辰名和俗称名。

  第32行,既然 getDoubleHour 函数有两个返回值,那么调用它的地方,也需要两个变量来接这两个返回值,就有了 shiChen, suCheng 这两个变量(也要逗号分隔) 等于函数返回值的语法。接的顺序就是两个变量位置与函数内返回时的位置从左至右一一对应。这样变量 shiChen 的值就是 时辰名,变量 suCheng 的值就是 俗称名。

  第33行,将字符串拼接中的两个变量换成 shiChen 和 suCheng 即可。

使用命名返回值函数的源代码

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
	"time"
)

// getDoubleHour 根据小时获得时辰名和俗称名
// 参数:
// hour,int 类型,传入 0~23 范围内的小时数字
// 返回值:
// 两个无名,字符串类型,依次是时辰名、俗称名
func getDoubleHour(hour int) (shiChen string, suCheng string) {
	name := [12][2]string{
		{"子时", "子夜"}, {"丑时", "鸡鸣"}, {"寅时", "黎明"}, {"卯时", "破晓"},
		{"辰时", "早晨"}, {"巳时", "上午"}, {"午时", "中午"}, {"未时", "日斜"},
		{"申时", "日馎"}, {"酉时", "傍晚"}, {"戌时", "黄昏"}, {"亥时", "深夜"},
	}
	num := 0

	if hour%2 == 0 { // 是偶数
		num = hour / 2
	} else if hour != 23 { // 是奇数且不等于 23
		num = (hour + 1) / 2
	}
	shiChen = name[num][0]
	suCheng = name[num][1]
	return
}

// 主函数,程序入口
func main() {
	shiChen, suCheng := getDoubleHour(time.Now().Hour())
	fmt.Println("现在是" + shiChen + ",俗称" + suCheng)
}

  上述代码编译运行,输出结果是正确的。

  第14行,两个返回值声明了变量,这两个变量在函数体内与参数一样,也是局部变量,可以直接给它们赋值。

  第27、28行,是分别给两个返回值变量赋值。

  第29行,只写一个 return 关键字就可以了,会自动将 shiChen 和 suCheng 两个变量的值返回去。这个特点在逻辑比较复杂的函数中十分有用。

分离文件继续模块化实践

  经过上面的修改,已将通过小时数返回时辰的功能独立成一个函数。但在大部分正式项目中代码量都是很大的,诸多功能交织在一起,如不很好的梳理会很繁杂。为了更好的代码重用,逻辑更清晰,维护更方便,通常 main 文件中尽量只放 main 函数,其他具体功能实现的代码都独立成包或文件(即模块化开发第二步)。那我们还是用这个示例进一步模块化实践。

  鼠标点击 LiteIDE 顶部菜单“文件(F)”,在下拉菜单中点击“新建…”,会弹出如下图的“新项目或文件窗口”:

在这里插入图片描述
  在 GOPATH 列表中选择正在使用的工作路径,然后在模板列表中选择 Go Source File 选项(Go语言源码文件),接着在名称输入框内输入文件主名,这里我输入的是 double_hour,文件名建议用小写加下划线格式。最后点击“OK”按钮,完成新源码文件 double_hour.go 的创建(扩展名是IDE自动加上去的)。这个方法创建的文件,与 main.go 在同一个文件夹下,所以应该使用同一个包名。新文件内输入如下代码:

// 计算时辰模块,文件名 double_hour.go
package main

// getDoubleHour 根据小时获得时辰名和俗称名
// 参数:
// hour,int 类型,传入 0~23 范围内的小时数字
// 返回值:
// 两个无名,字符串类型,依次是时辰名、俗称名
func getDoubleHour(hour int) (string, string) {
	name := [12][2]string{
		{"子时", "子夜"}, {"丑时", "鸡鸣"}, {"寅时", "黎明"}, {"卯时", "破晓"},
		{"辰时", "早晨"}, {"巳时", "上午"}, {"午时", "中午"}, {"未时", "日斜"},
		{"申时", "日馎"}, {"酉时", "傍晚"}, {"戌时", "黄昏"}, {"亥时", "深夜"},
	}
	num := 0

	if hour%2 == 0 { // 是偶数
		num = hour / 2
	} else if hour != 23 { // 是奇数且不等于 23
		num = (hour + 1) / 2
	}
	return name[num][0], name[num][1]
}

  这里的函数部分是从 main.go 里剪切过来的,不需要做任何修改。

  第2行声明的包名必须与 main.go 里声明的相同,因为与 main.go 在同一个文件夹下,不使用同样的包名会出错。

  剪切后的 main.go 文件内的代码就变成如下这样:

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
	"time"
)

// 主函数,程序入口
func main() {
	shiChen, suCheng := getDoubleHour(time.Now().Hour())
	fmt.Println("现在是" + shiChen + ",俗称" + suCheng)
}

  只是少了 getDoubleHour 函数部分,其他没有变。可能有同学会纳闷,没有导入刚才新建的文件,不会报错吗?事实是运行正常,原因就是 Go 语言编译的时候,是按照包名来组织代码的,同一个包名的多个文件,会如同在同一个文件内一样集中处理,所以包名相同的不同文件内的代码(必须是同文件内),是不需要导入的。

  经过这样改动,main.go 里的 main 函数就是主流程,其他各功能实现就分别在不同的包里或同名包里的其他文件里去实现,分工十分细致,条理更清晰。此乃模块化开发是也!

本节小结

  以下是对本节涉及的 Go 语言编程内容的归纳总结,方便记忆:

  ● Go 语言声明函数有以下几种常见形式:
  func 函数名 () { 函数体 },声明无参数、无返回值的函数;
  func 函数名 ( 参数名 类型 ) { 函数体 },声明有1个参数、无返回值的函数;
  func 函数名 ( 参数名 类型, 参数名 类型**) {** 函数体 },声明有2(多)个参数、无返回值的函数;
  func 函数名 ( 参数名 类型 ) 类型 { 函数体 },声明有1个参数、1个无名返回值的函数;
  func 函数名 ( 参数名 类型 )( 类型, 类型 ){ 函数体 },声明有1个参数、2(多)个无名返回值的函数;
  func 函数名 ( 参数名 类型 )( 变量名 类型 ){ 函数体 },声明有1个参数、1个命名返回值的函数;
  func 函数名 ( 参数名 类型 )( 变量名 类型, 变量名 类型 ){ 函数体 },声明有1个参数、2(多)个命名返回值的函数;
  参数名与返回值变量名都是该函数的局部变量。命名返回值变量不需要放在 return 后面,在执行 return 是,会自动返回。

  ● 将函数抽离到同包名的另一个文件内,不需要导入操作,Go 语言编译器会自动完成。

  ● 包含 main 函数的项目主文件,尽量不要放置与主流程无直接关系的代码,能让项目代码逻辑更清晰,更易梳理和维护。

  ● “模块化”开发很有必要,也是目前比较流行的“微服务”的理论基础,也可以说“微服务”是“模块化”开发的一种升华。
.
.
上一节:Go/Golang语言学习实践[回顾]教程07–通过时间判断时辰的示例【中】

下一节:Go/Golang语言学习实践[回顾]教程09–学习成绩统计的示例【上】
.

本文含有隐藏内容,请 开通VIP 后查看

微信公众号

今日签到

点亮在社区的每一天
去签到