GO语言设计模式 - 构建者模式
背景
假设我们需要实例化一个MySQL对象,在GO语言中,可以使用下面两种方式
逐个参数传递
func NewMySQL(a TypeA,b TypeB,c TypeC) MySQL
在GO语言中,函数是没有默认值的,这就意味着,假设NewMySQL函数有10个参数,所有的使用方必须传递满这是个参数,如果这勉强还能接受
那么,假设不值10个参数,后果可想而知。可以说这种办法几乎不可取,函数签名随着参数的个数变化不断变化
结构体传递
Type Params{ a TypeA b TypeB c TypeC } func NewMySQL(p Params) MySQL
这种方式比第一种好一点了,至少看起来是不用传递多个参数,函数签名也不用发生改变。这里我们把MySQL的实例化条件复杂一点,结构体层级化,看下使用方视角
opt := Params{ Endpoint: { IP: "xxxx", Port: "xxx", .... }, Timeout:{ ReadTimeout: "xxx", WriteTimeout: "xxx", ConnectTimeout: "xxx", .... }, .... //更多陌生的选项 } obj := new NewMySQL(opt);
这种方式对使用者不友好,创建对象时需要知道的细节太多,其次,代码的可读性也差。当然还有一个更致命的缺点,默认值问题
在GO里面变量声明的时候就等于初始值(int是0,string是"",bool为false),举例:当接受到的参数为0时候,不确定是用户有意传递的0还是用户没有传递结构体变量导致的0,程序很难做出判断。
构建者模式
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
模式的结构
建造者(Builder)模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
模式的实现
当然,不一定都要用完这几个角色,我们这里使用产品,抽象构建者类,具体构建者这几个角色,下面是一个完整例子
package main import "fmt" type mysqlopts struct { ip string port int readtimeout int } //抽象构建者 type MySqlOpt interface { SetIp(string) SetPort(int) SetReadTimeOut(int) Build() } type MySQLCli struct { opts mysqlopts } func (m *MySQLCli) SetIp(ip string) { m.opts.ip = ip } func (m *MySQLCli) SetPort(port int) { m.opts.port = port } func (m *MySQLCli) SetReadTimeOut(readtimeout int) { m.opts.readtimeout = readtimeout } func (m *MySQLCli) Build() { fmt.Println(m.opts.ip, m.opts.port, m.opts.readtimeout) } func NewMySQLCli() n { return &MySQLCli{} } func main() { objMySql := NewMySQLCli() objMySql.SetIp("127.0.0.1") objMySql.SetPort(3306) objMySql.SetReadTimeOut(10) objMySql.Build() //输出 127.0.0.1 3306 10 }
扩展:选项模式
上面的实现的这种方式其实在php或者java中很常见,但是在Go中,几乎很少使用上面的方式实现。而是使用Functional Options Pattern,中文名功能选项模式,该模式在众多 Go 开源项目中都有广泛使用,如 gRPC-go 项目
1.首先定义MySQL结构体
type mysqlClient struct { ip string port int readtimeout int }
2.针对MySQL结构体的选项
type Option func(mc *mysqlClient)
3.客户端实现,重点在于for循环,会循环选项,重新赋值
//实例化客户端 func NewClient(opts ...Option) (MySQL, error) { //这里处理默认参数 mc := &mysqlClient{ ip: "xxx", port: 0, readtimeout: 1, } for _, opt := range opts { opt(mc) } return mc, nil }
完整例子
package main import ( "fmt" ) type mysqlClient struct { ip string port int readtimeout int } //构建mysqlClient的选项模式 type Option func(mc *mysqlClient) //IP选项 func OptIP(ip string) Option { return func(mc *mysqlClient) { mc.ip = ip } } //Port选项 func OptPort(port int) Option { return func(mc *mysqlClient) { mc.port = port } } //readtimeout选项 func OptReadTimeout(readtimeout int) Option { return func(mc *mysqlClient) { mc.readtimeout = readtimeout } } //MySQL接口 type MySQL interface { connect() } //MySQL接口实现 func (mc *mysqlClient) connect() { fmt.Println(mc.ip, mc.port, mc.readtimeout) } //实例化客户端 func NewClient(opts ...Option) (MySQL, error) { //这里处理默认参数 mc := &mysqlClient{ ip: "xxx", port: 0, readtimeout: 1, } for _, opt := range opts { opt(mc) } return mc, nil } func main() { //不实用选项,使用内部默认值 clientEmpty, _ := NewClient() clientEmpty.connect() //使用选项 client, _ := NewClient( OptIP("127.0.0.1"), OptPort(3306), OptReadTimeout(50), ) client.connect() }
No Leanote account? Sign up now.