GO语言设计模式 - 构建者模式
2022-05-29 16:22:35    133    0    0
weibo-007

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)模式的主要角色如下。

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(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()
}

 

Pre: DDD系列-背景

Next: GO语言设计模式-工厂模式

133
Sign in to leave a comment.
No Leanote account? Sign up now.
0 comments
Table of content