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

目录
相关文章
|
1月前
|
Go Windows
|
4月前
|
消息中间件 设计模式 缓存
GO 中优雅编码和降低圈复杂度
GO 中优雅编码和降低圈复杂度
|
5月前
|
监控 NoSQL Go
GO 中 ETCD 的编码案例分享
GO 中 ETCD 的编码案例分享
|
6月前
|
设计模式 Kubernetes 监控
Go编程模式 - 8-装饰、管道和访问者模式
装饰、管道和访问者模式的使用频率不高,但在特定场景下会显得很酷
21 0
|
6月前
|
Kubernetes Shell Go
Go编程模式 - 7-代码生成
良好的命名能体现出其价值。尤其是在错误码的处理上,无需再去查询错误码对应的错误内容,直接可以通过命名了解。
28 0
|
6月前
|
SQL 分布式计算 Go
Go编程模式 - 6-映射、归约与过滤
但是,我不建议大家在实际项目中直接使用这一块代码,毕竟其中大量的反射操作是比较耗时的,尤其是在延迟非常敏感的web服务器中。 如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。
29 0
|
6月前
|
Go
Go编程模式 - 5.函数式选项
编程的一大重点,就是要 `分离变化点和不变点`。这里,我们可以将必填项认为是不变点,而非必填则是变化点。
15 0
|
6月前
|
Go
Go编程模式 - 4.错误处理
如何Wrap Error,在多人协同开发、多模块开发过程中,很难统一。而一旦不统一,容易出现示例中的过度Unwrap的情况。
19 0
|
6月前
|
Go
Go编程模式 - 3.继承与嵌入
业务逻辑依赖控制逻辑,才能保证在复杂业务逻辑变化场景下,代码更健壮!
22 0
|
6月前
|
JSON 前端开发 Go
Go编程模式 - 2.基础编码下
尽量用`time.Time`和`time.Duration`,如果必须用string,尽量用`time.RFC3339`
17 0