如果您以前没有使用过打字语言,那么乍一看它带来的力量可能并不明显。本文将向您展示如何利用类型系统使代码更易于使用和重用。
本文针对的是刚接触Go且几乎没有Go经验的开发人员。
对于本文,我们将研究如何处理分类数据。在这种情况下,特别是如何处理用于对书籍进行分类的体裁类别。
首先,我们将为Book定义一个数据结构,在该结构中,我们想通过体裁将其分类:
现在我们已经定义了这本书,让我们继续为流派定义一些常量:
const(Adventure =" Adventure" Comic =" Comic" Crime =" Crime" Fiction =" Fictionary =" Fantasy&# 34;历史="历史"恐怖="恐怖="魔术="魔术"神秘="神秘"哲学="哲学34;政治="政治&罗曼史="罗曼史"科学="科学"超级英雄="超级英雄"惊悚片="惊悚片&# 34; Western =" Western")
到目前为止,这似乎还不错。但是,类型常量是字符串。虽然这是非常人性化的读取代码的方式不是很有效,因为它与计算机程序有关。字符串将占用更多的存储空间和程序中的更多内存(更不用说我们是否将数百万的数据记录存储到数据库中)。因此,我们确实希望使用较小的数据类型来表示此数据。
在Go中,我们可以执行的一种方法是创建基于int类型的常量。
const(冒险= 1漫画= 2犯罪= 3小说= 4幻想= 5历史= 6恐怖= 7魔术= 8神秘= 9哲学= 10政治= 11浪漫= 12科学= 13超级英雄= 14惊悚= 15西方= 16 )
我们还需要更改Book结构,以现在将Genre表示为一个int:
尽管我们现在为Genre提供了更有效的记忆模型,但它并不是“人类”。友好。如果我打印出Book的值,我们现在将得到一个整数值。为了说明这一点,我们将编写一个快速测试来显示输出:
包booksimport(" testing")func TestGenre(t * testing.T){b:= Book {ID:1,Name:" All About Go&#34 ;,体裁:Magic,} t .logf("%+ v \ n&#34 ;, b)如果得到,exp:= b.Genre,8; != exp {t.Errorf("意外类型。得到%d,exp%d&#34 ;,得到,exp)}}
$ go test -v ./...=== RUN TestGenre books_test.go:14:{ID:1名称:All About Go Genre:8} ---通过:TestGenre(0.00s)PASSok github.com/gopherguides / corp / _blog / types /杠杆类型/ src / v2(已缓存)
请注意,流派仅显示值为8。每当我们调试代码或编写报告等时,我们现在都需要弄清楚8对人类实际上代表什么。
为此,我们可以编写一个使用Genre值的辅助函数,并确定什么是“人类”。表示应为:
func GenreToString(i int)字符串{切换i {情况1:return" Adventure"情况2:返回"漫画"情况3:返回“犯罪”情况4:返回“小说”情况5:返回“幻想”情况6:返回“历史”情况7:返回& Horror"情况8:返回" Magic"案例9:返回“神秘”情况10:返回“哲学”情况11:返回"政治"案例12:返回“浪漫”情况13:返回" Science"案例14:返回" Superhero"案例15:返回“惊悚片”情况16:返回" Western"默认值:返回"" }}
如果将来必须更改Genre的值,我们不仅必须更改常量值,而且还必须更新GenreToString函数。如果我们不这样做,将在我们的代码中创建一个错误。
我们不会利用类型系统来封装Genre的这种行为。我们将在短期内告诉您我们的意思。
我们真正需要做的第一件事是编写一个更具弹性的GenreToString函数。弹性是指即使将来Genre常数的值发生变化,GenreToString函数也不需要更改。
正确的方法不再是使用硬编码的值,而是使用常量本身的值:
func GenreToString(i int)字符串{切换我{案例冒险:return" Adventure" case Comic:return" Comic"案件犯罪:返还"犯罪"案例小说:return" Fiction"案例幻想:返回"幻想"案例历史记录:返回"历史记录"案例恐怖:返回“恐怖”案例魔术:返回"魔术" case Mystery:返回" Mystery"案例哲学的:返回"哲学的"政治案例:回报"政治"案例浪漫:返回"浪漫"案例科学:回报" Science"案例超级英雄:返回"超级英雄案例惊悚片:返回"惊悚片"西方案例:返回"西方"默认值:返回"" }}
好的,这更加简洁(而且可读),但是我们仍然没有解决以下事实:打印出来时,它显示的是数据值(int),而不是人类的34;可读值。
无需为Genre使用通用的int类型,我们可以基于现有类型创建自己的类型。在这种情况下,我们将基于int类型创建一个称为Genre的新类型:
const(冒险类型= 1漫画类型= 2犯罪类型= 3小说类型= 4幻想类型= 5历史类型= 6恐怖类型= 7魔术类型= 8神秘类型= 9哲学类型= 10政治类型= 11浪漫类型= 12科学类型= 13超级英雄类型= 14惊悚类型= 15西方类型= 16)
到目前为止,代码并没有真正的不同。但是,既然Genre是它自己的类型,我们可以为其添加方法。这使我们可以封装" human"我们想要的行为类型,而不是作为泛型函数。
func(g Genre)String()string {switch g {case Adventure:return" Adventure" case Comic:return" Comic"案件犯罪:返还"犯罪"案例小说:return" Fiction"案例幻想:返回"幻想"案例历史记录:返回"历史记录"案例恐怖:返回“恐怖”案例魔术:返回"魔术" case Mystery:返回" Mystery"案例哲学的:返回"哲学的"政治案例:回报"政治"案例浪漫:返回"浪漫"案例科学:回报" Science"案例超级英雄:返回"超级英雄案例惊悚片:返回"惊悚片"西方案例:返回"西方"默认值:返回"" }}
现在,当我们想了解什么是人类时,我们将能够使用String方法。类型的值是:
在Go中,如果您将String方法添加到任何类型,则fmt包现在将使用您的String方法进行" pretty print"您的类型的表示形式。因此,我们现在将看到,如果在测试中打印出该书,我们将获得“人类可读”的"类型:
func TestGenre(t * testing.T){b:= Book {ID:1,Name:" All About Go&#34 ;,体裁:Magic,} t.Logf("%+ v \ n& #34 ;, b)如果得到,则exp:= b.Genre.String()," Magic&#34 ;; != exp {t.Errorf("意外类型。得到%q,exp%q&#34 ;,得到,exp)}}
$ go test -v -run = TestGenre -count = 1。=== RUN TestGenre books_test.go:16:{ID:1名称:All About Go Genre:Magic} --- PASS:TestGenre(0.00s)PASSok book 0.059秒
现在,我们在打印输出中看到Genre的值为Magic,而不是8。同样重要的是要注意,我们的测试实际上并没有改变,只是我们对类型使用新类型的方式。
对于已经熟悉Go的那些人,您可能已经看过这个问题并问'为什么不只使用iota?"。 Iota是一个标识符,您可以在Go中使用它来创建递增的数字常量。尽管我在这里没有使用iota的原因有很多,但我确实将整个文章专门用于该主题。在Go中的何时何地使用Iota中阅读有关它的所有信息。
尽管此示例本质上是有目的的基本示例,但它说明了定义自己的类型并利用Go中的类型系统创建更具弹性,可读性和可重用代码的能力。
查阅我们之前的文章“拥抱Go类型系统”,并学习如何使用类型系统来避免Go中的常见错误。
您是寻找Go培训的公司吗?地鼠指南希望提供帮助。我们专注于讲师指导的虚拟围棋培训。
Go是一种类型化的语言,但是大多数Go开发人员并没有真正接受它。这篇简短的文章讨论了使用Go中的自定义类型编写更强大的代码的技巧。
编写软件的技巧是沟通的行为。您可能会觉得这是一个用顽强的编译器私下谈论代码的话题,但是软件的受众却更多。是使用您的库和API的人,是与您一起维护代码库的人,将来,您会寻找有关编写原因的线索。这个话题全是关于命名的。这是关于我们在程序中赋予事物的名称,以及这些决定如何影响我们编写的软件的可维护性。
编写单元测试的过程通常遵循某些步骤。首先,我们建立被测单元的依赖关系。接下来,我们执行被测逻辑单元。然后,我们将执行结果与我们的期望进行比较。最后,我们拆除所有依赖项,并将环境还原到找到的状态,以免影响其他单元测试。在Go 1.14中,测试包现在包括方法test。(* T).Cleanup,其目的是使创建和清除测试依赖项变得更加容易。