您现在的位置是:亿华云 > 应用开发

探讨两种 Option 编程模式的实现

亿华云2025-10-02 21:10:10【应用开发】0人已围观

简介option编程模式的引出在我们日常开发中,经常在初始化一个对象时需要进行属性配置,比如我们现在要写一个本地缓存库,设计本地缓存结构如下:type cache struct {// hashFunc

option编程模式的探讨引出

在我们日常开发中,经常在初始化一个对象时需要进行属性配置,两种比如我们现在要写一个本地缓存库,编程设计本地缓存结构如下:

type cache struct {

探讨两种 Option 编程模式的实现

// hashFunc represents used hash func

探讨两种 Option 编程模式的实现

HashFunc HashFunc

探讨两种 Option 编程模式的实现

// bucketCount represents the number of segments within a cache instance. value must be 模式a power of two.

BucketCount uint64

// bucketMask is bitwise AND applied to the hashVal to find the segment id.

bucketMask uint64

// segment is shard

segments []*segment

// segment lock

locks []sync.RWMutex

// close cache

close chan struct{ }

}

在这个对象中,字段hashFunc、探讨BucketCount是两种对外暴露的,但是编程都不是必填的,可以有默认值,模式针对这样的探讨配置,因为Go语言不支持重载函数,两种我们就需要多种不同的编程创建不同配置的缓存对象的方法:

func NewDefaultCache() (*cache,error){ }

func NewCache(hashFunc HashFunc, count uint64) (*cache,error) { }

func NewCacheWithHashFunc(hashFunc HashFunc) (*cache,error) { }

func NewCacheWithBucketCount(count uint64) (*cache,error) { }

这种方式就要我们提供多种创建方式,以后如果我们要添加配置,模式就要不断新增创建方法以及在当前方法中添加参数,探讨也会导致NewCache方法会越来越长,两种为了解决这个问题,编程我们就可以使用配置对象方案:

type Config struct {

HashFunc HashFunc

BucketCount uint64

}

我们把非必填的选项移动config结构体内,创建缓存的对象的方法就可以只提供一个,变成这样:

func DefaultConfig() *Config { }

func NewCache(config *Config) (*cache,error) { }

这样虽然可以解决上述的问题,但是也会造成我们在NewCache方法内做更多的云服务器提供商判空操作,config并不是一个必须项,随着参数增多,NewCache的逻辑代码也会越来越长,这就引出了option编程模式,接下来我们就看一下option编程模式的两种实现。

option编程模式一

使用闭包的方式实现,具体实现:

type Opt func(options *cache)

func NewCache(opts ...Opt) {

c := &cache{

close: make(chan struct{ }),

}

for _, each := range opts {

each(c)

}

}

func NewCache(opts ...Opt) (*cache,error){

c := &cache{

hashFunc: NewDefaultHashFunc(),

bucketCount: defaultBucketCount,

close: make(chan struct{ }),

}

for _, each := range opts {

each(c)

}

......

}

func SetShardCount(count uint64) Opt {

return func(opt *cache) {

opt.bucketCount = count

}

}

func main() {

NewCache(SetShardCount(256))

}

这里我们先定义一个类型Opt,这就是我们option的func型态,其参数为*cache,这样创建缓存对象的方法是一个可变参数,可以给多个options,我们在初始化方法里面先进行默认赋值,然后再通过for loop将每一个options对缓存参数的配置进行替换,这种实现方式就将默认值或零值封装在NewCache中了,新增参数我们也不需要改逻辑代码了。但是这种实现方式需要将缓存对象中的field暴露出去,这样就增加了一些风险,其次client端也需要了解Option的参数是什么意思,才能知道要怎样设置值,亿华云为了减少client端的理解度,我们可以自己提前封装好option函数,例如上面的SetShardCount,client端直接调用并填值就可以了。

option编程模式二

这种option编程模式是uber推荐的,是在第一版本上面的延伸,将所有options的值进行封装,并设计一个Option interface,我们先看例子:

type options struct {

hashFunc HashFunc

bucketCount uint64

}

type Option interface {

apply(*options)

}

type Bucket struct {

count uint64

}

func (b Bucket) apply(opts *options) {

opts.bucketCount = b.count

}

func WithBucketCount(count uint64) Option {

return Bucket{

count: count,

}

}

type Hash struct {

hashFunc HashFunc

}

func (h Hash) apply(opts *options) {

opts.hashFunc = h.hashFunc

}

func WithHashFunc(hashFunc HashFunc) Option {

return Hash{ hashFunc: hashFunc}

}

func NewCache(opts ...Option) (*cache,error){

o := &options{

hashFunc: NewDefaultHashFunc(),

bucketCount: defaultBucketCount,

}

for _, each := range opts {

each.apply(o)

}

.....

}

func main() {

NewCache(WithBucketCount(128))

}

这种方式我们使用Option接口,该接口保存一个未导出的方法,在未导出的options结构上记录选项,这种模式为client端提供了更多的灵活性,针对每一个option可以做更细的custom function设计,更加清晰且不暴露cache的结构,也提高了单元测试的覆盖性,缺点是当cache结构发生变化时,也要同时维护option的结构,维护复杂性升高了。高防服务器

总结

这两种实现方式都很常见,其都有自己的优缺点,采用闭包的实现方式,我们不需要为维护option,维护者的编码也大大减少了,但是这种方式需要export对象中的field,是有安全风险的,其次是client端需要了解对象结构中参数的意义,才能写出option参数,不过这个可以通过自定义option方法来解决;采用接口的实现方式更加灵活,每一个option都可以做精细化设计,不需要export对象中的field,并且很容易进行调试和测试,缺点是需要维护两套结构,当对象结构发生变更时,option结构也要变更,增加了代码维护复杂性。

实际应用中,我们可以自由变化,不能直接定义哪一种实现就是好的,凡事都有两面性,适合才是最好的。

很赞哦!(63)