Go编程模式 - 1.基础编码上

简介: Program to an interface, not an implementation

目录

Slice Internal

关于Slice的实现,我之前有一讲专门分析过底层实现。考虑到很多朋友没有细看,那我就再简单地讲一下。

type slice struct {
   
    array unsafe.Pointer // Slice底层保存数据的指针
    len int // 当前使用的长度
    cap int // 分配的长度
}

掌握Slice的底层实现,能让你真正理解一些看似“奇怪的”现象:

func main(){
   
    foo := make([]int, 5)
    foo[3] = 42
    foo[4] = 100

    bar := foo[1:4]
    bar[1] = 99

    fmt.Println(foo)
    // [0 0 99 42 100]
    fmt.Println(bar)
    // [0 99 42]
}

Tip: bar和foo是共享slice结构体底层的array的,所以修改了bar数组,foo也会变化

func main(){
   
    a := make([]int, 32)
    b := a[1:16]

    a = append(a, 1)
    a[2] = 42

    fmt.Println(b)
    // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
}

Tip: a和b原来是共享array的,但在a = append(a, 1)后发生了扩容,a和b指向的array发生了变化

 func main(){
   
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    dir2 := path[sepIndex+1:]
    fmt.Println(cap(dir1),cap(dir2))
    // 14 9
    fmt.Println("dir1 =>",string(dir1))
    // dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2))
    // dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    fmt.Println("dir1 =>",string(dir1))
    // dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2))
    // dir2 => uffixBBBB
}

Tip: 核心点在于理解dir1和dir2的cap分别是14和9。由于dir1的当前len=4,append的长度=6,4+6<14,所以不会发生扩容

Deep Comparison

我们先看一下示例,data结构体中四个注释为not comparable表示无法直接用 == 符号对比

type data struct {
   
    num    int               // ok
    checks [10]func() bool   // not comparable
    doit   func() bool       // not comparable
    m      map[string]string // not comparable
    bytes  []byte            // not comparable
}

func main() {
   
    v1 := data{
   }
    v2 := data{
   }
    fmt.Println("v1 == v2:", reflect.DeepEqual(v1, v2))
    // prints: v1 == v2: true

    m1 := map[string]string{
   "one": "a", "two": "b"}
    m2 := map[string]string{
   "two": "b", "one": "a"}
    fmt.Println("m1 == m2:", reflect.DeepEqual(m1, m2))
    // prints: m1 == m2: true

    s1 := []int{
   1, 2, 3}
    s2 := []int{
   1, 2, 3}
    fmt.Println("s1 == s2:", reflect.DeepEqual(s1, s2))
    // prints: s1 == s2: true
}

Tip: 示例比较复杂,其实要表达的内容比较简单:

函数、map、切片(不包括数组)以及它们的复合结构(如函数的数组),无法直接对比,只能用 reflect.DeepEqual

Function vs Receiver

type Person struct {
   
    Name   string
    Sexual string
    Age    int
}

func PrintPerson(p *Person) {
    fmt.Printf("Name=%s, Sexual=%s, Age=%d\n", p.Name, p.Sexual, p.Age) }
func (p *Person) Print()    {
    fmt.Printf("Name=%s, Sexual=%s, Age=%d\n", p.Name, p.Sexual, p.Age) }

func main() {
   
    var p = Person{
   
        Name: "Hao Chen", Sexual: "Male", Age: 44,
    }

    PrintPerson(&p)
    // Name=Hao Chen, Sexual=Male, Age=44
    p.Print()
    // Name=Hao Chen, Sexual=Male, Age=44
}

Tip: 示例比较简单,但其中蕴含的意义非常大,如对Person这个对象的抽象、简化代码等。

另外值得一提的是,Go编译器会根据方法 func (p *Person) Print() 的定义,将 p.Print()中的p从Person转换为*Person

Interface Patterns

这个模块非常重要,希望大家倒一杯水,细细品尝。

示例是一个很简单的interface实现,用来打印接口,我们看看代码。

type Country struct {
   
    Name string
}

type City struct {
   
    Name string
}

type Printable interface {
   
    PrintStr()
}

func (c Country) PrintStr() {
   
    fmt.Println(c.Name)
}

func (c City) PrintStr() {
   
    fmt.Println(c.Name)
}

func main() {
   
    c1 := Country{
   "China"}
    c2 := City{
   "Beijing"}

    var cList = []Printable{
   c1, c2}
    for _, v := range cList {
   
        v.PrintStr()
    }
}

那么,这时问题来了,如果我要实现N个Printable,就要定义N个strcut+N个PrintStr()方法。

前者的工作不能避免,而后者能否简化?那么示例来了

type WithName struct {
   
    Name string
}

type Country struct {
   
    WithName
}

type City struct {
   
    WithName
}

type Printable interface {
   
    PrintStr()
}

func (c WithName) PrintStr() {
   
    fmt.Println(c.Name)
}

func main() {
   
    c1 := Country{
   WithName{
   "China"}}
    c2 := City{
   WithName{
   "Beijing"}}

    var cList = []Printable{
   c1, c2}
    for _, v := range cList {
   
        v.PrintStr()
    }
}

Tip: 核心就是用 embedded 的特性来删除冗余的代码。当然,代价是初始化会稍微麻烦点。


这时候,陈皓又给出了一个例子,即打印的内容会根据具体的实现不同时,无法直接用WithName来实现

type Country struct {
   
    Name string
}

type City struct {
   
    Name string
}

type Printable interface {
   
    PrintStr()
}

func (c Country) PrintStr() {
   
    fmt.Println("Country:", c.Name)
}

func (c City) PrintStr() {
   
    fmt.Println("City:", c.Name)
}

func main() {
   
    c1 := Country{
   "China"}
    c2 := City{
   "Beijing"}

    var cList = []Printable{
   c1, c2}
    for _, v := range cList {
   
        v.PrintStr()
    }
}

首先,我们要明确是否有必要优化。如果只有示例中这么几行代码,我们完全没必要改写。那如果系统真复杂到一定程度,我们该怎么办呢?

这是一个很发散性的问题,我这里给出一个个人比较常用的解决方案,作为参考。

type WithTypeName struct {
   
    Type string
    Name string
}

type Country struct {
   
    WithTypeName
}

func NewCountry(name string) Printable {
   
    return Country{
   WithTypeName{
   "Country", name}}
}

type City struct {
   
    WithTypeName
}

func NewCity(name string) Printable {
   
    return City{
   WithTypeName{
   "City", name}}
}

type Printable interface {
   
    PrintStr()
}

func (c WithTypeName) PrintStr() {
   
    fmt.Printf("%s:%s\n", c.Type, c.Name)
}

func main() {
   
    c1 := NewCountry("China")
    c2 := NewCity("Beijing")

    var cList = []Printable{
   c1, c2}
    for _, v := range cList {
   
        v.PrintStr()
    }
}

Tip: 这种方法的好处有很多(先不谈弊端),比如可以将具体的实现CountryCity私有化,不对外暴露实现细节。今天不做细谈。

最后,送上一句经典:

Program to an interface, not an implementation

目录
相关文章
|
4月前
|
存储 NoSQL 测试技术
go最佳实践:如何舒适地编码
go最佳实践:如何舒适地编码
|
5月前
|
存储 Go API
一个go语言编码的例子
【7月更文挑战第2天】本文介绍Go语言使用Unicode字符集和UTF-8编码。Go中,`unicode/utf8`包处理编码转换,如`EncodeRune`和`DecodeRune`。`golang.org/x/text`库支持更多编码转换,如GBK到UTF-8。编码规则覆盖7位至21位的不同长度码点。
184 1
一个go语言编码的例子
|
7月前
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
187 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
7月前
|
监控 安全 Go
Go语言高效编码:利用上网行为管理软件实现实时警报系统
本文介绍了如何使用Go语言构建一个实时警报系统,用于监控和管理用户上网行为。通过定义监控规则(如使用正则表达式匹配特定网站),程序可以在检测到违规行为时触发警报。利用Go的goroutine实现并发处理,保证系统高效和实时。此外,系统还将警报信息自动提交至网站,便于管理员及时响应,提升网络安全和员工 productivity。
179 9
|
7月前
|
JSON 编解码 Go
【Go语言专栏】Go语言中的JSON编码与解码
【4月更文挑战第30天】Go语言内置JSON编码解码支持,简化了数据交换。`json.Marshal`用于将Go结构体转换为JSON,如示例中`Person`结构体的编码。`json.Unmarshal`则将JSON数据反序列化到结构体,需传入结构体变量的地址。错误处理至关重要,特别是在处理大量数据时,要注意性能优化,如避免不必要的转换和重复操作。了解自定义编码解码和最佳实践能提升开发效率。掌握这些技能,有助于构建高效Go应用。
72 0
|
7月前
|
Go Windows
|
消息中间件 设计模式 缓存
GO 中优雅编码和降低圈复杂度
GO 中优雅编码和降低圈复杂度
|
监控 NoSQL Go
GO 中 ETCD 的编码案例分享
GO 中 ETCD 的编码案例分享
|
设计模式 Kubernetes 监控
Go编程模式 - 8-装饰、管道和访问者模式
装饰、管道和访问者模式的使用频率不高,但在特定场景下会显得很酷
41 0
|
Kubernetes Shell Go
Go编程模式 - 7-代码生成
良好的命名能体现出其价值。尤其是在错误码的处理上,无需再去查询错误码对应的错误内容,直接可以通过命名了解。
68 0