Go设计模式(9)-建造者模式

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 这篇文章来讲解比较常用的创建型设计模式-建造者模式。建造者模式主要用来建造复杂的对象。

这篇文章来讲解比较常用的创建型设计模式-建造者模式。建造者模式主要用来建造复杂的对象。 本文UML类图链接为:https://www.processon.com/view/link/6080def6079129456d4beecf

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/9builder.go

1.定义

1.1建造者模式

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

UML类图:

1.2分析

要理解定义,得先理解定义中的“表示”。“表示”可以简单的认为是类中成员变量值不同。例如要展示一个长方形,需要左上角和右下角,如果这些值不一样,“表示”也就不一样。

Builder的作用就是建造Product,建造Product太过复杂,所以Builder中有多个BuildPart()用于组装Product的部分元素,通过GetResult()获取建造好的Product对象。

又因为Builder中的BuildPart()方法太多,如果直接给客户端使用的话,调用方做组装成本会比较高,所以有Director,它会利用Builder中众多BuildPart()方法,将Product组合起来。然后客户端就能够通过Builder的GetResult()方便的获取到组装好的Product,而无需知道组装的细节。

2.使用场景

对于建造者的使用场景,《大话设计模式》和《设计模式之美》中讲的都不是特别理想。《大话设计模式》使用的是造胖小人、瘦小人的例子,属于为了讲解而强出的一个例子,好处是完全符合UML类图。《设计模式之美》里使用构建复杂对象的例子,好处是例子确实很常见,内核也是建造者模式的内核,但是实现上并不很符合UML类图。思考再三,还是选择实际一点的例子吧,毕竟学习设计模式就是为了具体使用的。

假设我们要创建一个资源池,需要设置资源名称(name)、最大总资源数量(maxTotal)、最大空闲资源数量(maxIdle)、最小空闲资源数量(minIdle)等。其中name必填,maxTotal、maxIdle、minIdle非必填,但是填了一个其它两个也需要填,而且数据值有限制,如不能等于0,数据间有限制,如maxIdle不能大于maxTotal。

碰到这种问题如何处理呢?

我们可以使用构造函数,所有的判断都在构造函数里做。但是一旦输入参数很多,会导致调用的时候容易写乱,而且构造函数里判断太多,后面需求有变化,构造函数也需要更改,不满足开放封闭原则。

如果使用set,因为数据间有限制,很容易漏掉部分配置。而且有时资源对象为不可变对象,就不能暴露set方法。

这个时候,建造者模式就能发挥作用了。建造者将输入数据整理好,将数据以对象的方式传递给资源类的构造函数,资源类拿到数据直接获取数值即可,是不是就达到了分离的效果。今后有规则上的变动,只需要修改Builder即可。

3.代码实现

package main

import (
    "errors"
    "fmt"
)

/**
 * @Description: Product内的参数
 */
type ResourceParams struct {
    name     string
    maxTotal int64
    maxIdle  int64
    minIdle  int64
}

/**
 * @Description: Product接口
 */
type ResourceProduct interface {
    show()
}

/**
 * @Description: 实际Product,有show函数
 */
type RedisResourceProduct struct {
    resourceParams ResourceParams
}

/**
 * @Description: show成员函数,用于显示product的参数内容
 * @receiver p
 */
func (p *RedisResourceProduct) show() {
    fmt.Printf("Product的数据为 %+v ", p.resourceParams)
}

/**
 * @Description: 资源类创建接口
 */
type ResourceBuilder interface {
    setName(name string) ResourceBuilder
    setMaxTotal(maxTotal int64) ResourceBuilder
    setMaxIdle(maxIdle int64) ResourceBuilder
    setMinIdle(minIdle int64) ResourceBuilder
    getError() error
    build() (p ResourceProduct)
}

/**
 * @Description: 实际建造者
 */
type RedisResourceBuilder struct {
    resourceParams ResourceParams
    err            error
}

/**
 * @Description: 获取错误信息
 * @receiver r
 * @return error
 */
func (r *RedisResourceBuilder) getError() error {
    return r.err
}

/**
 * @Description: 设置名称
 * @receiver r
 * @param name
 * @return ResourceBuilder
 */
func (r *RedisResourceBuilder) setName(name string) ResourceBuilder {
    if name == "" {
        r.err = errors.New("name为空")
        return r
    }
    r.resourceParams.name = name
    fmt.Println("RedisResourceBuilder setName ", name)
    return r
}

/**
 * @Description: 设置maxTotal值,值不能小于0
 * @receiver r
 * @param maxTotal
 * @return ResourceBuilder
 */
func (r *RedisResourceBuilder) setMaxTotal(maxTotal int64) ResourceBuilder {
    if maxTotal <= 0 {
        r.err = errors.New("maxTotal小于0")
        return r
    }
    r.resourceParams.maxTotal = maxTotal
    fmt.Println("RedisResourceBuilder setMaxTotal ", maxTotal)
    return r
}

/**
 * @Description: 设置maxIdle值,值不能小于0
 * @receiver r
 * @param maxIdle
 * @return ResourceBuilder
 */
func (r *RedisResourceBuilder) setMaxIdle(maxIdle int64) ResourceBuilder {
    if maxIdle <= 0 {
        r.err = errors.New("maxIdle小于0")
        return r
    }
    r.resourceParams.maxIdle = maxIdle
    fmt.Println("RedisResourceBuilder setMaxIdle ", maxIdle)
    return r
}

/**
 * @Description: 设置minIdle值,值不能小于0
 * @receiver r
 * @param minIdle
 * @return ResourceBuilder
 */
func (r *RedisResourceBuilder) setMinIdle(minIdle int64) ResourceBuilder {
    if minIdle <= 0 {
        r.err = errors.New("minIdle小于0")
        return r
    }
    r.resourceParams.minIdle = minIdle
    fmt.Println("RedisResourceBuilder setMinIdle ", minIdle)
    return r
}

/**
 * @Description: 构建product
    1. 做参数校验
    2. 根据参数生成product
 * @receiver r
 * @return p
*/
func (r *RedisResourceBuilder) build() (p ResourceProduct) {
    // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
    if r.resourceParams.name == "" {
        r.err = errors.New("name为空")
        return
    }

    if !((r.resourceParams.maxIdle == 0 && r.resourceParams.minIdle == 0 && r.resourceParams.maxTotal == 0) ||
        (r.resourceParams.maxIdle != 0 && r.resourceParams.minIdle != 0 && r.resourceParams.maxTotal != 0)) {
        r.err = errors.New("数据需要保持一致")
        return
    }

    if r.resourceParams.maxIdle > r.resourceParams.maxTotal {
        r.err = errors.New("maxIdle > maxTotal")
        return
    }
    if r.resourceParams.minIdle > r.resourceParams.maxTotal || r.resourceParams.minIdle > r.resourceParams.maxIdle {
        r.err = errors.New("minIdle > maxTotal|maxIdle")
        return
    }
    fmt.Println("RedisResourceBuilder build")
    product := &RedisResourceProduct{
        resourceParams: r.resourceParams,
    }
    return product
}

/**
 * @Description: 指挥者
 */
type Director struct {
}

/**
 * @Description: 指挥者控制建造过程
 * @receiver d
 * @param builder
 * @return *ResourceProduct
 */
func (d *Director) construct(builder ResourceBuilder) ResourceProduct {
    resourceProduct := builder.setName("redis").
        setMinIdle(10).
        setMaxIdle(10).
        setMaxTotal(20).
        build()

    err := builder.getError()
    if err != nil {
        fmt.Println("构建失败,原因为" + err.Error())
        return nil
    }
    return resourceProduct
}

func main() {
    builder := &RedisResourceBuilder{}

    director := &Director{}
    product := director.construct(builder)

    if product == nil {
        return
    }

    product.show()
}

输出:

➜ myproject go run main.go

RedisResourceBuilder setName redis

RedisResourceBuilder setMinIdle 10

RedisResourceBuilder setMaxIdle 10

RedisResourceBuilder setMaxTotal 20

RedisResourceBuilder build

Product的数据为 {name:redis maxTotal:20 maxIdle:10 minIdle:10}

这段代码通过RedisResourceBuilder将Product的参数做了检查,使Product只需要关注自身的核心逻辑。Director负责组装,使调用方无需知道建造的细节。

如果需要更改为MySQL的资源,只需要创建MySQLResourceBuilder,然后实现接口函数即可。不过这种情况很少见。做的项目碰到的例子中,用在参数检验上相对多一些。

总结

如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。

  • 把类的必填属性放到构造函数中,强制创建对象的时候就设置
  • 类的属性之间有一定的依赖关系或者约束条件
  • 希望创建不可变对象

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

往期文章回顾:

招聘

  1. 字节跳动|抖音电商武汉服务端(高级)开发工程师
  2. 字节跳动|飞书大客户产品经理内推咯
  3. 字节跳动|抖音电商服务端技术岗位虚位以待
  4. 字节跳动招聘专题

设计模式

  1. Go设计模式(8)-抽象工厂
  2. Go设计模式(7)-工厂模式
  3. Go设计模式(6)-单例模式
  4. Go设计模式(5)-类图符号表示法
  5. Go设计模式(4)-代码编写优化
  6. Go设计模式(4)-代码编写
  7. Go设计模式(3)-设计原则
  8. Go设计模式(2)-面向对象分析与设计
  9. Go设计模式(1)-语法

语言

  1. Go工具之generate
  2. Go单例实现方案
  3. Go通道实现原理
  4. Go定时器实现原理
  5. Beego框架使用
  6. Golang源码BUG追查
  7. Gin框架简洁版
  8. Gin源码剖析

架构

  1. 支付接入常规问题
  2. 限流实现2
  3. 秒杀系统
  4. 分布式系统与一致性协议
  5. 微服务之服务框架和注册中心
  6. 浅谈微服务
  7. 限流实现1
  8. CDN请求过程详解
  9. 常用缓存技巧
  10. 如何高效对接第三方支付
  11. 算法总结

存储

  1. MySQL开发规范
  2. Redis实现分布式锁
  3. 事务原子性、一致性、持久性的实现原理
  4. InnoDB锁与事务简析

网络

  1. HTTP2.0基础教程
  2. HTTPS配置实战
  3. HTTPS连接过程
  4. TCP性能优化

工具

  1. GoLand实用技巧
  2. 根据mysql表自动生成go struct
  3. Markdown编辑器推荐-typora

读书笔记

  1. 原则
  2. 资治通鉴
  3. 敏捷革命
  4. 如何锻炼自己的记忆力
  5. 简单的逻辑学-读后感
  6. 热风-读后感
  7. 论语-读后感
  8. 孙子兵法-读后感

思考

  1. 为动员一切力量争取胜利而斗争
  2. 反对自由主义
  3. 实践论
  4. 评价自己的标准
  5. 服务端团队假期值班方案
  6. 项目流程管理
  7. 对项目管理的一些看法
  8. 对产品经理的一些思考
  9. 关于程序员职业发展的思考
  10. 关于代码review的思考
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
设计模式 Go 网络安全
[设计模式 Go实现] 结构型~代理模式
[设计模式 Go实现] 结构型~代理模式
|
1天前
|
设计模式 Go
[设计模式 Go实现] 结构型~装饰模式
[设计模式 Go实现] 结构型~装饰模式
|
1天前
|
设计模式 Go
[设计模式 Go实现] 结构型~适配器模式
[设计模式 Go实现] 结构型~适配器模式
|
1天前
|
设计模式 Go
[设计模式 Go实现] 行为型~迭代器模式
[设计模式 Go实现] 行为型~迭代器模式
|
1天前
|
设计模式 Go
[设计模式 Go实现] 行为型~职责链模式
[设计模式 Go实现] 行为型~职责链模式
|
13天前
|
设计模式 Java
小谈设计模式(14)—建造者模式
小谈设计模式(14)—建造者模式
|
1月前
|
设计模式 Java
设计模式之建造者模式
设计模式之建造者模式
|
1月前
|
设计模式 算法 程序员
【设计模式】建造者模式
【设计模式】建造者模式
|
2月前
|
设计模式 Java Go
Go常用设计模式(下)
Go常用设计模式(下)
25 0
|
2月前
|
设计模式 Go
Go常用设计模式(中)
Go常用设计模式(中)
17 0