From 6d079549f1194692a23df80d08d6b98f5385c20d Mon Sep 17 00:00:00 2001 From: Huangzj Date: Thu, 31 Dec 2020 14:48:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(Go-StudyExample):2020/12/31=EF=BC=9A?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=90Go=E6=AF=8F=E6=97=A5=E4=B8=80?= =?UTF-8?q?=E5=BA=93=E3=80=91go-cmp=E7=BB=93=E6=9E=84=E4=BD=93=E6=AF=94?= =?UTF-8?q?=E8=BE=83=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/go-cmp/Basic_test.go | 47 ++++++++++++ example/go-cmp/Diff_test.go | 34 +++++++++ example/go-cmp/DiyCompareMethod_test.go | 50 +++++++++++++ example/go-cmp/DiyEqualMethod_test.go | 47 ++++++++++++ example/go-cmp/Float_test.go | 58 +++++++++++++++ example/go-cmp/NoExported_test.go | 95 +++++++++++++++++++++++++ example/go-cmp/SliceMap_test.go | 50 +++++++++++++ example/go-cmp/Transformer_test.go | 75 +++++++++++++++++++ example/go-cmp/readme.md | 7 ++ readme.md | 2 + 10 files changed, 465 insertions(+) create mode 100644 example/go-cmp/Basic_test.go create mode 100644 example/go-cmp/Diff_test.go create mode 100644 example/go-cmp/DiyCompareMethod_test.go create mode 100644 example/go-cmp/DiyEqualMethod_test.go create mode 100644 example/go-cmp/Float_test.go create mode 100644 example/go-cmp/NoExported_test.go create mode 100644 example/go-cmp/SliceMap_test.go create mode 100644 example/go-cmp/Transformer_test.go create mode 100644 example/go-cmp/readme.md diff --git a/example/go-cmp/Basic_test.go b/example/go-cmp/Basic_test.go new file mode 100644 index 0000000..cc3ed2d --- /dev/null +++ b/example/go-cmp/Basic_test.go @@ -0,0 +1,47 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 14:43 + * @Description: + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "testing" +) + +//测试指向同一个结构体的对象 +func TestStruct(t *testing.T) { + u1 := User{Name: "dj", Age: 18} + u2 := User{Name: "dj", Age: 18} + + fmt.Println("u1 == u2?", u1 == u2) + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2)) + + c1 := &Contact{Phone: "123456789", Email: "dj@example.com"} + + u1.Contact = c1 + u2.Contact = c1 + fmt.Println("u1 == u2 with same pointer?", u1 == u2) + fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2)) + +} + +//测试指向不同结构体,但是结构体值一致的对象 +func TestPoint(t *testing.T) { + u1 := User{Name: "dj", Age: 18} + u2 := User{Name: "dj", Age: 18} + + fmt.Println("u1 == u2?", u1 == u2) + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2)) + + c1 := &Contact{Phone: "123456789", Email: "dj@example.com"} + c2 := &Contact{Phone: "123456789", Email: "dj@example.com"} + + u1.Contact = c1 + u2.Contact = c2 + fmt.Println("u1 == u2 with different pointer?", u1 == u2) + fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2)) +} diff --git a/example/go-cmp/Diff_test.go b/example/go-cmp/Diff_test.go new file mode 100644 index 0000000..a6a1df6 --- /dev/null +++ b/example/go-cmp/Diff_test.go @@ -0,0 +1,34 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 14:03 + * @Description:输出两个对象之间的差异 + */ + +package go_cmp + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" +) + +type Contact struct { + Phone string + Email string +} + +type User struct { + Name string + Age int + Contact *Contact +} + +func TestDiff(t *testing.T) { + c1 := &Contact{Phone: "123456789", Email: "dj@example.com"} + c2 := &Contact{Phone: "123456879", Email: "dj2@example.com"} + u1 := User{Name: "dj", Age: 18, Contact: c1} + u2 := User{Name: "dj2", Age: 18, Contact: c2} + + fmt.Println(cmp.Diff(u1, u2)) +} diff --git a/example/go-cmp/DiyCompareMethod_test.go b/example/go-cmp/DiyCompareMethod_test.go new file mode 100644 index 0000000..9fb7bcb --- /dev/null +++ b/example/go-cmp/DiyCompareMethod_test.go @@ -0,0 +1,50 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 14:57 + * @Description: + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "testing" +) + +type NetAddr1 struct { + IP string + Port int +} + +func compareNetAddr(a, b NetAddr1) bool { + if a.Port != b.Port { + return false + } + + if a.IP != b.IP { + if a.IP == "127.0.0.1" && b.IP == "localhost" { + return true + } + + if a.IP == "localhost" && b.IP == "127.0.0.1" { + return true + } + + return false + } + + return true +} + +//自定义比较器: +// 这种方式与上面介绍的自定义Equal()方法有些类似,但更灵活。 +// 有时,我们要自定义比较操作的类型定义在第三方包中,这样就无法给它定义Equal方法。 +// 这时,我们就可以采用自定义Comparer的方式。 +func TestDiyCompareMethod(t *testing.T) { + a1 := NetAddr1{"127.0.0.1", 5000} + a2 := NetAddr1{"localhost", 5000} + + fmt.Println("a1 equals a2?", cmp.Equal(a1, a2)) + fmt.Println("a1 equals a2 with comparer?", cmp.Equal(a1, a2, cmp.Comparer(compareNetAddr))) +} diff --git a/example/go-cmp/DiyEqualMethod_test.go b/example/go-cmp/DiyEqualMethod_test.go new file mode 100644 index 0000000..4d8f868 --- /dev/null +++ b/example/go-cmp/DiyEqualMethod_test.go @@ -0,0 +1,47 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 14:49 + * @Description:自定义Equal方法... + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "testing" +) + +type NetAddr struct { + IP string + Port int +} + +func (a NetAddr) Equal(b NetAddr) bool { + if a.Port != b.Port { + return false + } + + if a.IP != b.IP { + if a.IP == "127.0.0.1" && b.IP == "localhost" { + return true + } + + if a.IP == "localhost" && b.IP == "127.0.0.1" { + return true + } + + return false + } + + return true +} + +func TestDiyEqualMethod(t *testing.T) { + a1 := NetAddr{"127.0.0.1", 5000} + a2 := NetAddr{"localhost", 5000} + a3 := NetAddr{"192.168.1.1", 5000} + + fmt.Println("a1 equals a2?", cmp.Equal(a1, a2)) + fmt.Println("a1 equals a3?", cmp.Equal(a1, a3)) +} diff --git a/example/go-cmp/Float_test.go b/example/go-cmp/Float_test.go new file mode 100644 index 0000000..3cb5cab --- /dev/null +++ b/example/go-cmp/Float_test.go @@ -0,0 +1,58 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 15:11 + * @Description: + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "math" + "testing" +) + +type FloatPair struct { + X float64 + Y float64 +} + +func TestBasicFloat(t *testing.T) { + //特殊的浮点数NaN(Not a Number),它与任何浮点数都不等,包括它自己 + p1 := FloatPair{X: math.NaN()} + p2 := FloatPair{X: math.NaN()} + fmt.Println("p1 equals p2?", cmp.Equal(p1, p2)) + + //受限于变量的存储空间,就会存在误差 + f1 := 0.1 + f2 := 0.2 + f3 := 0.3 + p3 := FloatPair{X: f1 + f2} + p4 := FloatPair{X: f3} + fmt.Println("p3 equals p4?", cmp.Equal(p3, p4)) + + //Go 语言中这些字面量的运算直接是在编译器完成的,所以同样的值虽然受限于变量的存储空间,但是结果是一样的. + p5 := FloatPair{X: 0.1 + 0.2} + p6 := FloatPair{X: 0.3} + fmt.Println("p5 equals p6?", cmp.Equal(p5, p6)) +} + +//通过equal方法测试NaN返回结果一致 +func TestNanEqual(t *testing.T) { + p1 := FloatPair{X: math.NaN()} + p2 := FloatPair{X: math.NaN()} + fmt.Println("p1 equals p2?", cmp.Equal(p1, p2, cmpopts.EquateNaNs())) +} + +//测试误差在一定的范围内 具体表示是:|x-y| ≤ max(fraction*min(|x|, |y|), margin) +func TestCountError(t *testing.T) { + f1 := 0.1 + f2 := 0.2 + f3 := 0.3 + p3 := FloatPair{X: f1 + f2} + p4 := FloatPair{X: f3} + fmt.Println(fmt.Sprintf("实际存储的值分别是:\n %v \n %v", p3.X, p4.X)) + fmt.Println("p3 equals p4?", cmp.Equal(p3, p4, cmpopts.EquateApprox(0.1, 0.001))) +} diff --git a/example/go-cmp/NoExported_test.go b/example/go-cmp/NoExported_test.go new file mode 100644 index 0000000..536a5c4 --- /dev/null +++ b/example/go-cmp/NoExported_test.go @@ -0,0 +1,95 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 15:21 + * @Description:测试未导出字段 + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "reflect" + "testing" +) + +//自定义是否比较未导出字段,这个方法和上面的很类似,都是必须制定相应的结构体 +func TestDiyExportMethod(t *testing.T) { + u1 := User2{"dj", 18, Address{}} + u2 := User2{"dj", 18, Address{}} + + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Exporter(allowUnExportedInType))) +} + +//测试只忽略当前对象的未导出属性 +func TestExportOne(t *testing.T) { + c1 := Contactx{Phone: "123456789", Email: "dj@example.com"} + c2 := Contactx{Phone: "123456789", Email: "dj@example.com"} + + u1 := Userx{"dj", 18, c1} + u2 := Userx{"dj", 18, c2} + + //i think 通过 IgnoreUnexported 这个属性可以实现忽略具体类型的未导出字段,但是好像没办法忽略 【指针类型】 的未导出字段 + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmpopts.IgnoreUnexported(Userx{}))) +} + +//结构体中有未导出字段的时候,通过Equal进行比较的时候,会报错,除非设置忽略 +func TestExportError(t *testing.T) { + c1 := &Contact1{Phone: "123456789", Email: "dj@example.com"} + c2 := &Contact1{Phone: "123456789", Email: "dj@example.com"} + + u1 := User1{"dj", 18, c1} + u2 := User1{"dj", 18, c2} + + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2)) +} + +//使用 【IgnoreUnexported】,但是在结构体下层还有其他非导出的字段也会报错,这个字段没办法深入到里面 +func TestExportError2(t *testing.T) { + u11 := User2{"dj", 18, Address{}} + u22 := User2{"dj", 18, Address{}} + + fmt.Println("u1 equals u2?", cmp.Equal(u11, u22, cmpopts.IgnoreUnexported(User2{}))) +} + +func allowUnExportedInType(t reflect.Type) bool { + if t.Name() == "Address" { + return true + } + + return false +} + +type Contactx struct { + Phone string + Email string +} + +type Userx struct { + Name string + Age int + contact Contactx //注意这边是结构体 +} + +type Contact1 struct { + Phone string + Email string +} + +type User1 struct { + Name string + Age int + contact *Contact1 +} + +type Address struct { + Province string + city string +} + +type User2 struct { + Name string + Age int + Address Address +} diff --git a/example/go-cmp/SliceMap_test.go b/example/go-cmp/SliceMap_test.go new file mode 100644 index 0000000..6196bc1 --- /dev/null +++ b/example/go-cmp/SliceMap_test.go @@ -0,0 +1,50 @@ +/* + * @Author : huangzj + * @Time : 2020/12/30 14:08 + * @Description:map和切片的比较方法 + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" +) + +func TestNilEqualEmpty(t *testing.T) { + var s1 []int + var s2 = make([]int, 0) + + var m1 map[int]int + var m2 = make(map[int]int) + + //Go原生的" == "符号是没办法比较 切片和map的. + //fmt.Println(s1 == s2) + //fmt.Println(m1 == m2) + + //这边可以通过Equal来比较 切片和map + fmt.Println("s1 equals s2?", cmp.Equal(s1, s2)) + fmt.Println("m1 equals m2?", cmp.Equal(m1, m2)) + + //空的切片和nil如果要比较相等,需要加上对应的参数 + fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.EquateEmpty())) + fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.EquateEmpty())) + +} + +//比较无序的切片,和本来就没有顺序的map +func TestSliceOutOfOrder(t *testing.T) { + s1 := []int{1, 2, 3, 4} + s2 := []int{4, 3, 2, 1} + fmt.Println("s1 equals s2?", cmp.Equal(s1, s2)) + fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.SortSlices(func(i, j int) bool { return i < j }))) + + fmt.Println() + + m1 := map[int]int{1: 10, 2: 20, 3: 30} + m2 := map[int]int{1: 10, 2: 20, 3: 30} + fmt.Println("m1 equals m2?", cmp.Equal(m1, m2)) + fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.SortMaps(func(i, j int) bool { return i < j }))) +} diff --git a/example/go-cmp/Transformer_test.go b/example/go-cmp/Transformer_test.go new file mode 100644 index 0000000..3d50b8a --- /dev/null +++ b/example/go-cmp/Transformer_test.go @@ -0,0 +1,75 @@ +/* + * @Author : huangzj + * @Time : 2020/12/31 14:33 + * @Description: 比较的时候转换对应的属性,改变结构体对比的规则 + */ + +package go_cmp + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "testing" +) + +type UserO struct { + Name string + Age int +} + +func omitAge(u UserO) string { + return u.Name +} + +type UserO2 struct { + Name string + Age int + Email string + Address string +} + +func omitAge2(u UserO2) UserO2 { + return UserO2{u.Name, 0, u.Email, u.Address} +} + +//对某个字段忽略比较,也就是返回字段默认值 +func TestOmit(t *testing.T) { + u1 := UserO{Name: "dj", Age: 18} + u2 := UserO{Name: "dj", Age: 28} + + //i think 这边omitAge只返回name属性应该是值比较UserO的name属性即可 + fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Transformer("omitAge", omitAge))) + + u3 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"} + u4 := UserO2{Name: "dj", Age: 28, Email: "dj@example.com"} + + u5 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"} + u6 := UserO2{Name: "dj1", Age: 28, Email: "dj@example.com"} + + //i think 这边的omitAge2返回修改之后的UserO2,只把age设置为0,其他属性不变,说明还是会参与到比较 + fmt.Println("u3 equals u4?", cmp.Equal(u3, u4, cmp.Transformer("omitAge", omitAge2))) + + fmt.Println("u5 equals u5?", cmp.Equal(u5, u6, cmp.Transformer("omitAge", omitAge2))) +} + +type NetAddrO struct { + IP string + Port int +} + +func transformLocalhost(a NetAddrO) NetAddrO { + if a.IP == "localhost" { + return NetAddrO{IP: "127.0.0.1", Port: a.Port} + } + + return a +} + +//对结构体进行对比的时候,修改某字段的值 +func TestTransformValue(t *testing.T) { + a1 := NetAddrO{"127.0.0.1", 5000} + a2 := NetAddrO{"localhost", 5000} + + //这边是把NetAddr对应的IP进行转换,localhost == 127.0.0.1 所以直接进行转换,比较的结果就会一致 + fmt.Println("a1 equals a2?", cmp.Equal(a1, a2, cmp.Transformer("localhost", transformLocalhost))) +} diff --git a/example/go-cmp/readme.md b/example/go-cmp/readme.md new file mode 100644 index 0000000..dc7eb04 --- /dev/null +++ b/example/go-cmp/readme.md @@ -0,0 +1,7 @@ +获取包命令:go get -u github.com/google/go-cmp + +参考地址:https://segmentfault.com/a/1190000021997004 + +参考地址:https://github.com/google/go-cmp + +参考地址:https://github.com/darjun/go-daily-lib \ No newline at end of file diff --git a/readme.md b/readme.md index 167ae79..e224f3a 100644 --- a/readme.md +++ b/readme.md @@ -43,6 +43,8 @@ json与struct之间转换处理工具使用示例 2020/12/29:新增【Go每日一库】emial发送邮件 +2020/12/31:新增【Go每日一库】go-cmp结构体比较工具 + # mod vendor模式加载包 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到