Golang 编程思维和工程实战

发布于 2021-11-04 15:16 ,所属分类:软件编程学习资料


一 Golang 编程思维

  • 首先,我们先来看下最基本的,就是 Golang 的学习技巧,比如:
    • 通读 Golang 的一些好的文章如Frequently Asked Questions (FAQ)或者看看FAQ 的中文翻译,主要是了解 Golang 的全貌。
      • Go 精华文章列表
      • Go 相关博客列表
      • Go Talks


      主要是能够在具体的编码中有迹可循
      参考业界大牛们的代码,主要是看一些开源的优质的项目,比如 Google 他们这帮人自己搞的 Kubernetes、Istio,还有一些好的项目如 Docker、CoreDNS、etcd 等等。

      • 项目基本架构的组织
      • 代码基本的编码封装
      • 代码的基本原则规范
      • 并发的设计思想
      • 面向对象编程的设计思想
      • 可扩展性的设计思想

      • 然后就是实践,实实在在的跑一些代码示例,可以自己建立一个 base-code 的项目,里面就是你的各种示例,然后进行一些修改、执行。

      具体的代码示例可以从官方文档上来,推荐Go by Example,里面有大量非常好的例子。

      也可以自己网上随便搜下,重要的自己要修改并执行,查看和分析结果。

      • Go 101
  • 其次,要理解 Golang 编程思维,首先要理解 Golang 这门语言的创始初衷,初衷就是为了解决好 Google 内部大规模高并发服务的问题,主要核心就是围绕高并发来开展;并且同时又不想引入面向对象那种很复杂的继承关系。
    • 首先,就是可以方便的解决好并发问题(包括高并发),那么就需要有并发思维,能够并发处理就通过并发来进行任务分配
      • 这个就是涉及到了 context、 goroutine、channel(select)
      • 可以创建大量 goroutine, 但是需要能通过 context、 channel 建立 "父子"关系,保证子任务可以能够被回收、被主动控制(如 杀死)

      • 再者,面向对象编程思想,利用好 interface、 struct 来实现继承、多态的用法
      • struct 匿名组合来实现继承
      • interface 和 struct 来实现多态
      • interface 定义接口,尽可能的保持里面的方法定义简单,然后多个 interface 进行组合

  • 然后,理解 Golang 语言本身的一些特性:
    • 强类型,语法上要注意处理
    • GC,实际中要观察 GC 日志并分析
    • 注意语法语义尽可能的简单、保持各种类型定义尽可能精简

    • 最后,从 Golang 社区的一些最佳实践来看,Golang 的各种组件需要尽可能的精简。

    • Golang 中用好的一些开源组件库,都是比较轻量级的,然后可以各自随意组合来达到最佳实践。
    • 我们自己进行组件封装、模块封装的时候,也是保持这个原则,尽可能的精简,然后使用方进行组合。

    二 Golang 高级编码技巧

    1 优雅的实现构造函数编程思想

    /*
    一个更为优雅的构造函数的实现方式

    参考:
    https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html

    通过这个方式可以方便构造不同对象,同时避免了大量重复代码

    */

    package main

    import (
    "fmt"
    "time"

    "golang.org/x/net/context"
    )

    type Cluster struct {
    opts options
    }

    type options struct {
    connectionTimeout time.Duration
    readTimeout time.Duration
    writeTimeout time.Duration
    logError func(ctx context.Context, err error)
    }

    // 通过一个选项实现为一个函数指针来达到一个目的:设置选项中的数据的状态
    // Golang函数指针的用法
    type Option func(c *options)


    // 设置某个参数的一个具体实现,用到了闭包的用法。
    // 不仅仅只是设置而采用闭包的目的是为了更为优化,更好用,对用户更友好
    func LogError(f func(ctx context.Context, err error)) Option {
    return func(opts *options) {
    opts.logError = f
    }
    }

    // 对关键数据变量的赋值采用一个方法来实现而不是直接设置
    func ConnectionTimeout(d time.Duration) Option {
    return func(opts *options) {
    opts.connectionTimeout = d
    }
    }

    func WriteTimeout(d time.Duration) Option {
    return func(opts *options) {
    opts.writeTimeout = d
    }
    }

    func ReadTimeout(d time.Duration) Option {
    return func(opts *options) {
    opts.readTimeout = d
    }
    }

    // 构造函数具体实现,传入相关Option,new一个对象并赋值
    // 如果参数很多,也不需要传入很多参数,只需要传入opts ...Option即可
    func NewCluster(opts ...Option) *Cluster {
    clusterOpts := options{}
    for _, opt := range opts {
    // 函数指针的赋值调用
    opt(&clusterOpts)
    }

    cluster := new(Cluster)
    cluster.opts = clusterOpts

    return cluster
    }

    func main() {

    // 前期储备,设定相关参数
    commonsOpts := []Option{
    ConnectionTimeout(1 * time.Second),
    ReadTimeout(2 * time.Second),
    WriteTimeout(3 * time.Second),
    LogError(func(ctx context.Context, err error) {
    }),
    }

    // 终极操作,构造函数
    cluster := NewCluster(commonsOpts...)

    // 测试验证
    fmt.Println(cluster.opts.connectionTimeout)
    fmt.Println(cluster.opts.writeTimeout)

    }

    除了构造函数这个思想之外,还有一个思想,就是我们要善于利用 struct 封装对象方法,然后再 new 一个对象出来,如下:
    type Cluster struct {
    opts options
    }

    func NewCluster(opts ...Option) *Cluster {
    ....

    cluster := new(Cluster)
    cluster.opts = clusterOpts

    return cluster
    }

    2 优雅的实现继承编程思想

    Golang 里面没有 C++ 、Java 那种继承的实现方式,但是,我们可以通过 Golang 的匿名组合来实现继承,这里要注意,这个是实际编程中经常用到的一种姿势。具体实现就是一个 struct 里面包含一个匿名的 struct,也就是通过匿名组合,这最基础的基类就是一个 struct 结构,然后定义相关成员变量,然后再定义一个子类,也是一个 struct,里面包含前面的 struct,即可实现继承。
    示例代码如下,这个是我实际项目(大型 IM 架构)中的实现方式,代码里面有详细的解释:
    package main

    import (
    "fmt"
    )

    // 【基类】
    //定义一个最基础的struct类MsgModel,里面包含一个成员变量msgId
    type MsgModel struct {
    msgId int
    msgType int
    }

    // MsgModel的一个成员方法,用来设置msgId
    func (msg *MsgModel) SetId(msgId int) {
    msg.msgId = msgId
    }

    func (msg *MsgModel) SetType(msgType int) {
    msg.msgType = msgType
    }

    //【子类】
    // 再定义一个struct为GroupMsgModel,包含了MsgModel,即组合,但是并没有给定MsgModel任何名字,因此是匿名组合
    type GroupMsgModel struct {
    MsgModel

    // 如果子类也包含一个基类的一样的成员变量,那么通过子类设置和获取得到的变量都是基类的
    msgId int
    }

    func (group *GroupMsgModel) GetId() int {
    return group.msgId
    }

    /*
    func (group *GroupMsgModel) SetId(msgId int) {
    group.msgId = msgId
    }
    */

    func main() {
    group := &GroupMsgModel{}

    group.SetId(123)
    group.SetType(1)

    fmt.Println("group.msgId =", group.msgId, "\tgroup.MsgModel.msgId =", group.MsgModel.msgId)
    fmt.Println("group.msgType =", group.msgType, "\tgroup.MsgModel.msgType =", group.MsgModel.msgType)
    }



相关资源