在go的我的项目中,大家编码的时候应该或多或少都看过go的一些源码或者其余开源我的项目的源码,不晓得大家有没有感觉本人写进去的代码绝对于源码有肯定的差距,不论从构造定义,接口封装等方面总感觉差那么点意思。反正我始终感觉本人的编码绝对于go的源码差距较大。既然有这么好的代码在背后,咱们如何依据这些源码提取一些供本人学习的内容呢,这次筹备依据源码好好学习一下,简略总结如下,如果后续还有心得会逐渐更新。

最近正好用到的两个开源代码:
1、github.com/olivere/elastic/v7
2、google.golang.org/grpc

本人的我的项目调用这两个包时,有如下的代码(如果有趣味能够本人去github上看残缺的源码)
1、用elastic包构建聚合查问

    agg := elastic.NewTermsAggregation().        Size(10000).        Field("data.sce")

2、用grpc结构连贯

conn, err := grpc.DialContext(connCtx, "127.0.0.1:5555", grpc.WithInsecure(), grpc.WithBlock())    if err != nil {        return err    }

抛开两个函数的性能不谈,其实两个函数都能够了解为一种结构或者初始化函数,其Size,Field,WithInsecure,WithBlock等函数都是在初始化或者结构时提供某种参数而已。

那咱们提炼一下,就是当咱们结构一个对象时,个别会提供很多参数用来结构,然而不同场景,或者不同条件下,须要的参数又不同,如何来封装这些构造函数来方便使用呢?

以如下构造为例,假如是一个代理

type Agency struct { IP       int32         //required Protocol string        //optional Timeout  time.Duration //optional}

办法一:

间接结构不同的构造函数:

func NewAgency(ip int32) *Agencyfunc NewAgencyWithProtocol(ip int32, p string) *Agencyfunc NewAgencyWithProtocolAndTimeout(ip int32, p string, t time.Duration) *Agency......

你会发现须要定义一系列的函数,而且随着参数增多,可能裁减的函数也比拟多,同时因为go中不反对多态,每个函数还要有不同的名称,当你这次调用了A,下次减少参数时,还得改为调用B,可见麻烦多多,既不难看,也不好用,还不好保护。当然理论我的项目中可能也没人会这么写,这里只是举例而已。

办法二:

间接将Agency构造体作为参数,这样参数不就固定了吗,而且一举解决所有问题,只用一个构造函数即可。

func NewAgency(a Agency) *Agency

这种办法事实我的项目中的确也有应用的哦,那对于这种简略的构造体,还算比拟不便简洁,然而如果构造体成员较多(像goroutine等构造),动辄20+以上,那你初始化的时候还得先一一确定参数,而后再去调用构造函数是不是也麻烦,而且很多参数其实用不到赋值,只有默认值就够用了。而且成员变量一多,你可能都不晓得那些是必选参数,那些是可选参数了。
那如何解决这个问题呢?

办法三:

批改Agency的构造体,将可选成员和必选成员离开:

type AgencyOption struct {    Protocol string        //optional    Timeout  time.Duration //optional    TLS      tls.Conn      //optional}type Agency struct {    IP       int32         //required    AgencyOption}

而后定义一个构造函数

func NewAgency(ip int32, param *AgencyOption) *Agency

这个函数辨别了必选项和可选项,ip必填,param可选,如果param为nil则不必对可选参数赋值。
这种计划绝对于上一个办法略有改良,对于必选参数高深莫测,然而对于参数较多的场景还是没有基本解决。

办法四:

间接将可选参数不放在构造函数中,定义多个设置函数,例如:

func (a *Agency)SetProtocol(p string) {   a.Protocol = p}func (a *Agency)SetTimeout(t time.Duration) {   a.Timeout = t}

调用者应用形式:

a1 := NewAgency(ip)a1.SetProtocol("udp")a1.SetTimeout(100)

办法五:

上述办法须要屡次调用,咱们做个批改:

func (a *Agency)SetProtocol(p string) *Agency{   a.Protocol = p   return a}func (a *Agency)SetTimeout(t time.Duration) *Agency{   a.Timeout = t   return a}

这样,调用者能够应用链式调用:

a1 := NewAgency(ip).SetTimeout(100).SetProtocol("udp")

这也是一种罕用的办法。
像文章开篇提到的elastic库就是这么玩的,每次查问的时候可能有很多参数要设置,间接间断调用即可,很清晰,这也是从这个开源库中学到。当前就能够间接利用到理论我的项目中了哦!
那如果就想将参数一把传入,一次性初始化实现呢?

办法六:

一次性传入任意多个参数,首先咱们能够想到go反对变参,例如func Add(base int, others ...int),能够解决任意个数的int类型,然而咱们的参数个别是不一样的,那咱们如何利用这种计划呢?
咱们大胆设想一下如果有这么一种通用类型能够应用(先不思考返回值):

func NewAgency(ip int32, options ...Option)

如果能实现这样一个构造函数,那么可选参数的问题也就搞定了!!
然而这个通用的Option如何定义呢??应用某一种具体类型必定是不能实现的,那是否能够将这个Option定义为一个函数或者接口类型呢?

先从函数思考,看是否实现:
一个函数,无非是函数名,入参,逻辑解决,返回值这些东东
函数名,whatever,轻易起个能自正文的即可;
入参,先放一下;
逻辑解决,就是这个函数要做啥,想想咱们的最终目标就是将可选参数设置到咱们的对象中去!!那么这个逻辑解决就相似于:

agency.Timeout = time以及agency.Protocol = "udp"等等...

从逻辑解决看咱们要将参数设置到对象中,那这个对象是不是能够作为咱们的独特参数,那么这个Option的定义是不是能够为:

type Option func(a *Agency)

因为是要扭转Agency中的值,所以用的是指针作为入参。

既然Option定义好了,那么针对每个参数咱们来实现由参数如何转换为这种Option传入构造函数吧,即创立入参是参数,然而返回值是Option类型的函数
对于超时工夫:

func Timeout(t time.Duration) Option{   return func(a *Agency){      a.Timeout = t   }}

对于协定设置:

func Protocol(p string) Option{   return func(a *Agency){      a.Protocol = p   }}

构造函数为:

func NewAgency(ip int32, options ...Option) *Agency {   a := &Agency{IP:ip}   for _, opt := range options{      opt(a)   }      return a}

调用者为:

a1 := NewAgency(ip)a2 := NewAgency(ip, Timeout(100))a4 := NewAgency(ip, Timeout(200), Protocol("udp"))

这样就清晰明了了吧,功败垂成!

办法七:

上个办法中定义的Option是一个函数类型,那么接口类型是否也能够胜任呢?
答案也是能够的,这个是我从grpc的实现反向思考的过程,大家能够参考,或者间接撸grpc的源码看:)
咱们还是以Agency为例
首先定义这个Option接口,因为在go中通常定义的接口名都带er,咱们也遵循传统定义为:

type Optioner interface {   apply()}

接口先只定义了一个名字,入参和返回值待定。
既然有了接口,那么咱们就要定义一个类型来实现这个接口:

type RealOption struct {}func (ro *RealOption)apply(){}

雏形就有了,那么如何来填这些定义的内容呢?
先别急,咱们持续定义设置参数的函数,返回值类型都要为Optioner,所以其模型相似为:

func SetProtocol(p string) Optioner {   return &RealOption{   }}func SetTimeout(t time.Duration) Optioner {   return &RealOption{   }}

再持续看咱们最终可能提供的构造函数,应该是这个样子:

func NewAgency(ip int32, options ...Optioner) *Agency {   a := &Agency{IP:ip}   for _, opt := range options{      opt.apply()   }   return a}

外围还是遍历可变参数options,去调用对应的接口设置相应的参数,从这里作为突破口,那么apply这个接口类型应该定义成什么呢,是不是跃然纸上了,只有减少个入参,无需返回值

apply(agency *Agency)

有了入参后,上述波及参数的各个定义批改为:

func (ro *RealOption)apply(agency *Agency){}type Optioner interface {   apply(agency *Agency)}func NewAgency(ip int32, options ...Optioner) *Agency {   a := &Agency{IP:ip}   for _, opt := range options{      opt.apply(a)   }   return a}

既然接口定义好了,那么看如何实现参数设置函数的逻辑,对于SetTimeout函数,目标是将入参t传到对象中去,那么就相似于:

func SetTimeout(t time.Duration) Optioner {   return &RealOption{      ?:t,   }}

如果?的地位是具体的类型,那么这个t其实是设置到了RealOption中,并没有设置到Agency中,那么怎么办呢,还记得上个办法中的Option定义吗 ,其就是一个通用类型的函数,将参数设置到Agency里,那么这个中央也用这种形式呢,即:

func SetTimeout(t time.Duration) Optioner {   return &RealOption{      ?:func(agency *Agency){         agency.Timeout = t      },   }}

那这样RealOption的定义也就进去了:

type RealOption struct {   f func(agency *Agency)}

那么所有的内容根本都实现了,整体代码如下:

type Optioner interface {   apply(agency *Agency)}type RealOption struct {   f func(agency *Agency)}func (ro *RealOption) apply(agency *Agency) {   ro.f(agency)}func SetProtocol(p string) Optioner {   return &RealOption{      f: func(agency *Agency) {         agency.Protocol = p      },   }}func SetTimeout(t time.Duration) Optioner {   return &RealOption{      f: func(agency *Agency) {         agency.Timeout = t      },   }}func NewAgency(ip int32, options ...Optioner) *Agency {   a := &Agency{IP: ip}   for _, opt := range options {      opt.apply(a)   }   return a}

调用形式跟上一种办法一样,不过传入的可选参数是接口而已
持续优化一下,对RealOption构造也提供一个构造函数,那么参数设置函数改为:

func NewRealOption(f func(agency *Agency)) *RealOption {   return &RealOption{      f: f,   }}func SetProtocol(p string) Optioner {   return NewRealOption(func(agency *Agency) {      agency.Protocol = p   })}func SetTimeout(t time.Duration) Optioner {   return NewRealOption(func(agency *Agency) {      agency.Timeout = t   })}

OK,功败垂成!这种办法就对应了开篇提到的grpc中实现的办法。

看似很简略的一个性能,好的开源代码总会采纳各种形式,值得咱们在理论我的项目中多多领会,如果大家有什么更好的办法欢送留言交换分享。