简单工厂

业务逻辑->基础方法的依赖关系, 拆分为 业务逻辑->实例创建->基础方法, 后面添加实例(基础方法)类型时,不需要修改业务逻辑,符合单一职责

为什么需要简单工厂

举个例子来说, 如果我们要接入阿里oss,并且预计后续会有私有部署时使用的minio(本地搭建的oss服务)或者其他厂商的oss服务

这里我们先写第一版接入阿里oss

package main

import (
    "fmt"
    "io"
)

type AliOSS struct {
    SecretId  string
    SecretKey string
    Bucket    string
}

// UploadFile 上传文件到阿里oss
func (AliOSS) UploadFile(localFilePath, objectKey string) error {
    fmt.Println("alioss upload file")
    return nil
}

// GetFile 下载/获取阿里oss文件
func (AliOSS) GetFile(objectKey string) (io.Reader, error) {
    fmt.Println("alioss get file")
    return nil, nil
}

// GetSignUrl 获取阿里oss临时访问地址
func (AliOSS) GetSignUrl(objectKey string) (string, error) {
    fmt.Println("alioss get sign url")
    return "", nil
}

func main() {
    // 创建逻辑
    alioss := AliOSS{
        SecretId:  "alioss_secret_id",
        SecretKey: "alioss_secret_key",
        Bucket:    "alioss_env_buckect_name",
    }

    // 业务逻辑
    alioss.UploadFile("/tmp/test.txt", "tmp/test.txt")
    alioss.GetFile("tmp/test.txt")
    alioss.GetSignUrl("tmp/test.txt")
}

我们第一版上线啦, 这样看上去还是很nice的

····· 时间流逝, 一个月过去了, 半年过去了

产品:”有个私有定制的项目,需要部署到客户内部机器中, 数据不能存到外面, 所以相关地方需要支持本地存储”

这时发现了oss使用的阿里oss,这需要修改成本地的, 于是开始调研哪些成熟的oss库支持本地搭建存储, 偶然间你突然发现了minio的存在, 可以本地搭建,速度也不赖, 该有的功能也全都支持, 这不正合需求嘛, 在你熟悉了提供的官方文档之后,果断撸起袖子加油干, 争取早日上线给客户使用

······ 时光荏苒, 一个月过去了, 私有部署的所有相关修改完成了, 关于你的oss代码成了这样:

package main

import (
    "fmt"
    "io"
    "math/rand"
)

type AliOSS struct {
    EndPoint  string // 服务地址
    SecretId  string // 秘钥id
    SecretKey string // 秘钥key
    Bucket    string // 桶
}

// UploadFile 上传文件到阿里oss
func (AliOSS) UploadFile(localFilePath, objectKey string) error {
    fmt.Println("alioss upload file")
    return nil
}

// GetFile 下载/获取阿里oss文件
func (AliOSS) GetFile(objectKey string) (io.Reader, error) {
    fmt.Println("alioss get file")
    return nil, nil
}

// GetSignUrl 获取阿里oss临时访问地址
func (AliOSS) GetSignUrl(objectKey string) (string, error) {
    fmt.Println("alioss get sign url")
    return "", nil
}

type Minio struct {
    EndPoint string // 服务地址
    User     string // 用户
    Password string // 密码
    Bucket   string // 桶
}

// UploadFile 上传文件到minio
func (Minio) UploadFile(localFilePath, objectKey string) error {
    fmt.Println("minio upload file")
    return nil
}

// GetFile 下载/获取minio文件
func (Minio) GetFile(objectKey string) (io.Reader, error) {
    fmt.Println("minio get file")
    return nil, nil
}

// GetSignUrl 获取minio临时访问地址
func (Minio) GetSignUrl(objectKey string) (string, error) {
    fmt.Println("minio get sign url")
    return "", nil
}

func main() {
    // 获取配置文件判断是否私有部署
    isPrivate := rand.Intn(2)
    if isPrivate == 1 {
        minio := Minio{
            EndPoint: "minio_endpoint",
            User:     "minio_user",
            Password: "minio_password",
            Bucket:   "minio_env_bucket",
        }
        // 业务逻辑
        minio.UploadFile("/tmp/test.txt", "tmp/test.txt")
        minio.GetFile("tmp/test.txt")
        minio.GetSignUrl("tmp/test.txt")
    } else {
        alioss := AliOSS{
            EndPoint:  "alioss_endpoint",
            SecretId:  "alioss_secret_id",
            SecretKey: "alioss_secret_key",
            Bucket:    "alioss_env_buckect_name",
        }
        // 业务逻辑
        alioss.UploadFile("/tmp/test.txt", "tmp/test.txt")
        alioss.GetFile("tmp/test.txt")
        alioss.GetSignUrl("tmp/test.txt")
    }
}

这时就没有上次那么nice了, main方法大了很多, 包含了根据是否私有化走不同oss逻辑, 既包含了创建的内容,也包含了业务逻辑的内容, 这还只是第二版,如果后期再迭代,那么main方法中的代码和逻辑会更庞杂,该如何优化呢?

  • 将两个分支逻辑拆开封装成一个方法
    但是会有两个问题
    • 创建和业务逻辑还是耦合的
    • 具有重复的业务逻辑代码

那如何解决上面两个问题呢? 将创建和业务逻辑拆分,解耦合, 还有减少重复的业务逻辑代码

  • 增加一个代理方法,里面根据不同if/else条件, 返回不同的实例类,然后根据接口提供的方法进行业务逻辑的实现, 降低了创建和业务逻辑的耦合性

  • 声明一个oss接口,指定有哪些功能方法,main方法中使用实现接口的实例类方法, 这样可以减少业务逻辑代码

    秘籍来了, 请用心感受下面代码对于上面两个问题的处理思想

package main

import (
    "fmt"
    "io"
    "math/rand"
)

// oss 接口指定实现类需要提供哪些能力
type OSS interface {
    UploadFile(localFilePath, objectKey string) error
    GetFile(objectKey string) (io.Reader, error)
    GetSignUrl(objectKey string) (string, error)
}

type AliOSS struct {
    EndPoint  string // 服务地址
    SecretId  string // 秘钥id
    SecretKey string // 秘钥key
    Bucket    string // 桶
}

// UploadFile 上传文件到阿里oss
func (AliOSS) UploadFile(localFilePath, objectKey string) error {
    fmt.Println("alioss upload file")
    return nil
}

// GetFile 下载/获取阿里oss文件
func (AliOSS) GetFile(objectKey string) (io.Reader, error) {
    fmt.Println("alioss get file")
    return nil, nil
}

// GetSignUrl 获取阿里oss临时访问地址
func (AliOSS) GetSignUrl(objectKey string) (string, error) {
    fmt.Println("alioss get sign url")
    return "", nil
}

type Minio struct {
    EndPoint string // 服务地址
    User     string // 用户
    Password string // 密码
    Bucket   string // 桶
}

// UploadFile 上传文件到minio
func (Minio) UploadFile(localFilePath, objectKey string) error {
    fmt.Println("minio upload file")
    return nil
}

// GetFile 下载/获取minio文件
func (Minio) GetFile(objectKey string) (io.Reader, error) {
    fmt.Println("minio get file")
    return nil, nil
}

// GetSignUrl 获取minio临时访问地址
func (Minio) GetSignUrl(objectKey string) (string, error) {
    fmt.Println("minio get sign url")
    return "", nil
}

// 生成实例的代理工厂
type Factory struct{}

// 生成实例的不同判断逻辑
func (f Factory) CreateOSS() OSS {
    // 获取配置文件判断是否私有部署
    isPrivate := rand.Intn(2)
    switch isPrivate {
    case 1:
        return f.createMinio()
    default:
        return f.createAliOSS()
    }
}

// createMinio 生成minio的实例
func (Factory) createMinio() *Minio {
    // (省略)获取配置文件中minio的相关配置
    return &Minio{
        EndPoint: "minio_endpoint",
        User:     "minio_user",
        Password: "minio_password",
        Bucket:   "minio_env_bucket",
    }
}

// createAliOSS 生成alioss的实例
func (Factory) createAliOSS() *AliOSS {
    return &AliOSS{
        EndPoint:  "alioss_endpoint",
        SecretId:  "alioss_secret_id",
        SecretKey: "alioss_secret_key",
        Bucket:    "alioss_env_buckect_name",
    }
}

func main() {
    // 创建实例
    oss := Factory{}.CreateOSS()
    // 业务逻辑
    oss.UploadFile("/tmp/test.txt", "tmp/test.txt")
    oss.GetFile("tmp/test.txt")
    oss.GetSignUrl("tmp/test.txt")
}

如果后期如果再加其他oss, 我们只需要实现oss的接口方法, 并且添加到创建代理工厂中,而不影响业务逻辑中的代码了,是不是很nice呢!

简单工厂的思想

这便是简单工厂模式的思想

  • 提供一个工厂(代理),完成创建工作, 减少创建和业务逻辑代码的耦合性
  • 通过接口定义, 业务逻辑面相接口实现功能逻辑, 增加其他类型不影响业务代码, 只需要实现接口的方法以及注册到工厂中即可

因为在上面已经详细推导过过程,不再叙述工厂模式了, 只要记得 接口+工厂(代理工厂)解耦创建和业务逻辑就行, 提供一个类图:

简单工厂

什么时候用

例子围绕oss一个具体场景来使用简单工厂, 在日常工作中遇到的肯定是五花八门的, 提供一个技巧,什么时候需要考虑用简单工厂

简单工厂属于创建型, 围绕创建解决问题, 故而重点应该放在创建上, 如果有相同功能的不同类型(minio、alioss都是oss上传下载)场景下, 可以将创建这个功能(逻辑) 改成一个简单工厂, 后面的业务逻辑不依赖不同实例产生不同分支

优缺点

优点

  • 符合单一职责, 业务逻辑和创建功能拆分
  • 面向接口, 业务逻辑针对oss的接口进行逻辑处理, 这样再添加类型的时候不影响业务逻辑

缺点

  • 违反开闭原则, 增加一个类型,需要修改工厂内容
  • 随着类型增加,工厂越来越庞大,类型实例也会增加, 增加代码复杂度
本作品采用《CC 协议》,转载必须注明作者和本文链接
我是哈哈怪,每天笑哈哈. go/php开发, 业余喜欢写技术文章,欢迎关注我的个人网站 公众号:哈哈怪玩编程
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!