option编程模式在我们日常的开发中,往往需要在初始化一个对象的时候配置属性。比如我们现在要写一个本地缓存库,将本地缓存结构设计如下:值必须是二的幂。BucketCountuint64//bucketMask按位与应用于hashVal以查找段ID。bucketMaskuint64//segment是shardsegments[]*segment//segmentlocklocks[]sync.RWMutex//closecacheclosechanstruct{}}在这个对象中,暴露了字段hashFunc和BucketCount,但都不是必需的和可以有一个默认值。对于这样的配置,由于Go语言不支持函数重载,我们需要多种不同的方式来创建不同配置的缓存对象:)(*cache,error){}funcNewCacheWithHashFunc(hashFuncHashFunc)(*cache,error){}funcNewCacheWithBucketCount(countuint64)(*cache,error){}这个方法是我们需要提供的多种创建方法。如果我们以后要添加配置,就必须不断的添加创建方法,给当前方法添加参数,这样也会导致NewCache方法越来越长。为了解决这个问题,我们可以使用配置对象方案:typeConfigstruct{HashFuncHashFuncBucketCountuint64}我们把不需要的选项移到config结构中,只能提供一个创建缓存对象的方法,变成这样:funcDefaultConfig()*Config{}funcNewCache(config*Config)(*cache,error){}这样虽然可以解决上面的问题,但是也会导致我们在NewCache方法中做更多的空判断操作。配置不是必须的。随着参数个数的增加,NewCache的逻辑代码也会越来越长,这就导致了option编程方式。下面我们来看看期权编程模式的两种实现方式。选项编程模式是使用闭包实现的。具体实现:typeOptfunc(options*cache)funcNewCache(opts...Opt){c:=&cache{close:make(chanstruct{}),}for_,each:=rangeopts{each(c)}}funcNewCache(opts...Opt)(*cache,error){c:=&cache{hashFunc:NewDefaultHashFunc(),bucketCount:defaultBucketCount,close:make(chanstruct{}),}for_,每个:=rangeopts{each(c)}…}funcSetShardCount(countuint64)Opt{returnfunc(opt*cache){opt.bucketCount=count}}funcmain(){NewCache(SetShardCount(256))}这里我们首先定义一个类型Opt,它是我们optionstate的func类型,它的参数是*cache,所以创建缓存对象的方法是一个可变参数,可以给多个option,我们先在初始化方法中赋默认值,然后通过for循环替换将每个选项配置到缓存参数中,这种实现会将默认值或零值封装在NewCache中,我们不需要更改新参数的逻辑代码。但是这种实现方式需要暴露缓存对象中的字段,增加了一些风险。其次,客户端也需要了解Option参数的含义,才能知道如何设置值。为了减少客户端的理解,我们可以提前自己封装option函数,比如上面的SetShardCount,客户端直接调用填值即可。选项编程模式2这种选项编程模式是uber推荐的。它是第一个版本的扩展。它封装了所有options的值,设计了一个Option接口。我们先来看例子:.count}funcWithBucketCount(countuint64)选项{returnBucket{count:count,}}typeHashstruct{hashFuncHashFunc}func(hHash)apply(opts*options){opts.hashFunc=h.hashFunc}funcWithHashFunc(hashFuncHashFunc)Option{returnHash{hashFunc:hashFunc}}funcNewCache(opts...Option)(*cache,error){o:=&options{hashFunc:NewDefaultHashFunc(),bucketCount:defaultBucketCount,}对于_,每个:=范围选择{每个。apply(o)}.....}funcmain(){NewCache(WithBucketCount(128))}这样我们使用了Option接口,在未导出的options结构体上保存了一个未导出的方法和记录选项。这种模式为客户端提供了更多的灵活性,可以为每个选项做更详细的自定义功能设计,更清晰且不暴露缓存的结构,同时也提高了单元测试的覆盖率。缺点是当缓存结构发生变化时,必须同时维护option的结构,增加了维护的复杂度。综上所述,这两种实现方式都很通用,各有优缺点。有了闭包实现,我们就不需要维护option,大大减少了维护者的编码,但是这个方法需要export对象。场,存在安全隐患。其次,客户端在写option参数前需要了解对象结构中参数的含义。不过这个可以通过自定义option方法来解决;接口的实现更加灵活,每个选项都有可以做细粒度设计,不需要导出对象中的字段,易于调试和测试。缺点是需要维护两套结构体。当对象结构改变时,选项结构也需要改变,增加了代码维护的复杂度。在实际应用中,我们可以随意更改,不能直接定义哪种实现是最好的。任何事物都有两个方面,最合适的就是最好的。
