熟悉 Python 开发的同学都知道,Python 有默认参数的存在,使得我们在实例化一个对象的时候,可以根据需要来选择性的覆盖某些默认参数,以此来决定如何实例化对象。当一个对象有多个默认参数时,这个特性非常好用,能够优雅地简化代码。

而 Go 语言从语法上是不支持默认参数的,所以为了实现既能通过默认参数创建对象,又能通过传递自定义参数创建对象,我们就需要通过一些编程技巧来实现。对于这些程序开发中的常见问题,软件行业的先行者们总结了许多解决常见场景编码问题的最佳实践,这些最佳实践后来成为了我们所说的设计模式。其中选项模式在 Go 语言开发中会经常用到。

通常我们有以下三种方法来实现通过默认参数创建对象,以及通过传递自定义参数创建对象:

  • 使用多个构造函数

  • 默认参数选项

  • 选项模式

通过多构造函数实现

第一种方式是通过多构造函数实现,下面是一个简单例子:

package mainimport "fmt"const (defaultAddr = "127.0.0.1"defaultPort = 8000
)type Server struct {Addr stringPort int
}func NewServer() *Server {return &Server{Addr: defaultAddr,Port: defaultPort,}
}func NewServerWithOptions(addr string, port int) *Server {return &Server{Addr: addr,Port: port,}
}func main() {s1 := NewServer()s2 := NewServerWithOptions("localhost", 8001)fmt.Println(s1)  // &{127.0.0.1 8000}fmt.Println(s2)  // &{localhost 8001}
}

这里我们为 Server 结构体实现了两个构造函数:

  • NewServer:无需传递参数即可直接返回 Server 对象

  • NewServerWithOptions :需要传递 addr 和 port 两个参数来构造 Server 对象

如果通过默认参数创建的对象即可满足需求,不需要对 Server 进行定制时,我们可以使用 NewServer 来生成对象(s1)。而如果需要对 Server 进行定制时,我们则可以使用 NewServerWithOptions 来生成对象(s2)。

通过默认参数选项实现

另外一种实现默认参数的方案,是为要生成的结构体对象定义一个选项结构体,用来生成要创建对象的默认参数,代码实现如下:

package mainimport "fmt"const (defaultAddr = "127.0.0.1"defaultPort = 8000
)type Server struct {Addr stringPort int
}type ServerOptions struct {Addr stringPort int
}func NewServerOptions() *ServerOptions {return &ServerOptions{Addr: defaultAddr,Port: defaultPort,}
}func NewServerWithOptions(opts *ServerOptions) *Server {return &Server{Addr: opts.Addr,Port: opts.Port,}
}func main() {s1 := NewServerWithOptions(NewServerOptions())s2 := NewServerWithOptions(&ServerOptions{Addr: "localhost",Port: 8001,})fmt.Println(s1)  // &{127.0.0.1 8000}fmt.Println(s2)  // &{localhost 8001}
}

我们为 Server 结构体专门实现了一个 ServerOptions 用来生成默认参数,调用 NewServerOptions 函数即可获得默认参数配置,构造函数 NewServerWithOptions 接收一个 *ServerOptions 类型作为参数。所以我们可以通过以下两种方式来完成功能:

  • 直接将调用 NewServerOptions 函数的返回值传递给 NewServerWithOptions 来实现通过默认参数生成对象(s1)

  • 通过手动构造 ServerOptions 配置来生成定制对象(s2)

通过选项模式实现

以上两种方式虽然都能够完成功能,但却有以下缺点:

  • 通过多构造函数实现的方案需要我们在实例化对象时分别调用不同的构造函数,代码封装性不强,会给调用者增加使用负担。

  • 通过默认参数选项实现的方案需要我们预先构造一个选项结构,当使用默认参数生成对象时代码看起来比较冗余。

而选项模式可以让我们更为优雅地解决这个问题。代码实现如下:

package mainimport "fmt"const (defaultAddr = "127.0.0.1"defaultPort = 8000
)type Server struct {Addr stringPort int
}type ServerOptions struct {Addr stringPort int
}type ServerOption interface {apply(*ServerOptions)
}type FuncServerOption struct {f func(*ServerOptions)
}func (fo FuncServerOption) apply(option *ServerOptions) {fo.f(option)
}func WithAddr(addr string) ServerOption {return FuncServerOption{f: func(options *ServerOptions) {options.Addr = addr},}
}func WithPort(port int) ServerOption {return FuncServerOption{f: func(options *ServerOptions) {options.Port = port},}
}func NewServer(opts ...ServerOption) *Server {options := ServerOptions{Addr: defaultAddr,Port: defaultPort,}for _, opt := range opts {opt.apply(&options)}return &Server{Addr: options.Addr,Port: options.Port,}
}func main() {s1 := NewServer()s2 := NewServer(WithAddr("localhost"), WithPort(8001))s3 := NewServer(WithPort(8001))fmt.Println(s1)  // &{127.0.0.1 8000}fmt.Println(s2)  // &{localhost 8001}fmt.Println(s3)  // &{127.0.0.1 8001}
}

乍一看我们的代码复杂了很多,但其实调用构造函数生成对象的代码复杂度是没有改变的,只是定义上的复杂。

我们定义了 ServerOptions 结构体用来配置默认参数。因为 Addr 和 Port 都有默认参数,所以 ServerOptions 的定义和 Server 定义是一样的。但有一定复杂性的结构体中可能会有些参数没有默认参数,必须让用户来配置,这时 ServerOptions 的字段就会少一些,大家可以按需定义。

同时,我们还定义了一个 ServerOption 接口和实现了此接口的 FuncServerOption 结构体,它们的作用是让我们能够通过 apply 方法为 ServerOptions 结构体单独配置某项参数。

我们可以分别为每个默认参数都定义一个 WithXXX 函数用来配置参数,如这里定义的 WithAddr 和 WithPort ,这样用户就可以通过调用 WithXXX 函数来定制需要覆盖的默认参数。

此时默认参数定义在构造函数 NewServer 中,构造函数的接收一个不定长参数,类型为 ServerOption,在构造函数内部通过一个 for 循环调用每个传进来的 ServerOption 对象的 apply 方法,将用户配置的参数依次赋值给构造函数内部的默认参数对象 options 中,以此来替换默认参数,for 循环执行完成后,得到的 options 对象将是最终配置,将其属性依次赋值给 Server 即可生成新的对象。

总结

通过 s2 和 s3 的打印结果可以发现,使用选项模式实现的构造函数更加灵活,相较于前两种实现,选项模式中我们可以自由的更改其中任意一项或多项默认配置。

虽然选项模式确实会多写一些代码,但多数情况下这都是值得的。比如 Google 的 gRPC 框架 Go 语言实现中创建 gRPC server 的构造函数 NewServer 就使用了选项模式,感兴趣的同学可以看下其源码的实现思想其实和这里的示例程序如出一辙。

以上就是我关于 Golang 选项模式的一点经验,希望今天的分享能够给你带来一些帮助。

Golang 常见设计模式之选项模式相关推荐

  1. 常见设计模式—抽象工厂模式

    设计模式-抽象工厂模式 1.什么是抽象工厂模式 抽象工厂模式是围绕一个超级工厂创建其它工厂,是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品. 2.角色分 ...

  2. Golang 常见设计模式之装饰模式

    想必只要是熟悉 Python 的同学对装饰模式一定不会陌生,这类 Python 从语法上原生支持的装饰器,大大提高了装饰模式在 Python 中的应用.尽管 Go 语言中装饰模式没有 Python 中 ...

  3. PHP 常见设计模式——工厂模式

    最近参加了几次面试,对于应用常见的几种设计模式问题,深有感触.为加强自身理解,同时也希望能给一些初级开发者一定的参考学习,决定开始就PHP常见的设计模式写下几篇博文,工作原因,会不定期更新,感谢您的耐 ...

  4. JavaScript 中常见设计模式整理

    开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式.本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知. JavaScript 中常见设计模 ...

  5. Android常见设计模式——代理模式(Proxy Pattern)(二)

    文章目录 1. 前言 2. 远程代理(Remote Proxy) 3. 后记 1. 前言 在上篇Android常见设计模式--代理模式(Proxy Pattern)中基本上知道了什么是代理模式,以及对 ...

  6. Golang之函数选项模式

    仅做记录 /*Functional Options函数选项模式(简称FOP模式)既保持了兼容性,而且每增加1个新属性只需要1个With函数即可,大大减少了修改代码的风险 */ package main ...

  7. 5 修改request对象变量_【总结】前端5大常见设计模式,代码一看你就懂!

    前言 今天主要介绍一下我们平常会经常用到的设计模式,设计模式总的来说有23种,而设计模式在前端中又该怎么运用呢,接下来主要对比较前端中常见的设计模式做一个介绍. 设计模式的定义 设计模式是在面向对象软 ...

  8. 研磨设计模式之 策略模式--转

    http://www.uml.org.cn/sjms/201009092.asp 研磨设计模式之 策略模式   2010-09-09 作者:云飞龙行 来源:云飞龙行的blog   先感谢众多朋友的支持 ...

  9. php 代码修改后 重新实例化_从匿名函数到PHP设计模式之容器模式

    点击蓝字关注我们!每天获取最新的编程小知识! 源 / php中文网      源 / www.php.cn 从匿名函数(闭包特性)到 PHP 设计模式之容器模式 (查看原文请点击本文末尾左下角: 匿名 ...

最新文章

  1. 如何用C#动态编译、执行代码
  2. 解决: AttributeError: module 'cv2' has no attribute 'SURF'
  3. 程序员工资为什么高?
  4. 专利实力论互联网巨头造车:中美格局初定,百度苹果跨洋辉映
  5. PHP----------安装包lnmp1.3-full安装的lnmp环境,如何安装PHP扩展
  6. gRPC真要取代WebApi了,你还学得过来吗?
  7. 存储计算解耦合,构建中国人英语语音数据库
  8. [Android系列—] 2. Android 项目目录结构与用户界面的创建
  9. python实现删除文件与目录的方法
  10. Spring 源码解析!
  11. CentOS 如何修改mysql 用户root的密码
  12. 我在HW中用到的三款工具
  13. 局域网计算机名和ip扫描工具,局域网IP扫描器(Advanced IP Scanner)
  14. 答粉丝问|火狐浏览器插件简介
  15. 使用cephadm搭建ceph(octopus)过程
  16. c语言中dot作用,Unix中的dot命令详解
  17. 这8种恶心虫子 你可能每天都在吃!
  18. xilinx vivado 2019 cordic ip 计算sin cos
  19. 斯坦福大学秋季课程《深度学习理论》STATS 385开讲
  20. 使用ESP32驱动ST7789,效果很好的IPS显示屏

热门文章

  1. 缺省参数-缺省参数的注意事项
  2. gtest 测试java_LangTest
  3. drop 很慢 物化视图_终于解决了物化视图复制的问题
  4. nginx配置文件详细解读
  5. python 内置递归
  6. 阿里云免费申请免费SSL证书
  7. 如何提高码农产量,基于ASP.NET MVC的敏捷开发框架之自定义表单开发随笔四
  8. 关于Html中jsp调用Android中方法无效的一点建议
  9. HDU - 5381 The sum of gcd(莫队/线段树区间合并)
  10. CodeForces - 787D - Legacy(线段树优化建图+最短路)