工厂模式

回顾简单工厂,我们将创建oss(产品)实例的代码解耦到工厂中进行,实现了业务逻辑和创建的解耦,也避免了代码的重复。但是有没有发现创建的工厂中还是存在了一堆的if/else或是switch判断语句,这些和创建实际是无关的,但是被耦合到工厂里面,若是后面再添加一种oss类型,实现产品的基础上,我们还需要修改工厂,因为他耦合了switch的分支判断, 很明显违反了开闭原则(对扩展是开放的,对修改是关闭的)

工厂模式解决了什么

而工厂模式正是解决了简单模式中违反开闭原则的缺点,换句话来说 工厂模式 =简单工厂+解决开闭原则,着重将工厂中判断分支逻辑解耦, 工厂中只剩下创建实例的部分, 那么到底是如何解决的呢,看下根据简单工厂的例子进行的优化:

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 interface {
    CreateOSS() OSS
}

// 每个产品提供一个创建工厂
type AliOSSFactory struct{}

// CreateOSS  生成alioss 实例的工厂
func (AliOSSFactory) CreateOSS() *AliOSS {
    // (省略)获取配置文件中minio的相关配置
    return &AliOSS{
        EndPoint:  "alioss_endpoint",
        SecretId:  "alioss_secret_id",
        SecretKey: "alioss_secret_key",
        Bucket:    "alioss_env_buckect_name",
    }
}

type MinioFactory struct{}

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

func main() {
    // 获取配置文件判断是否私有部署
    var oss OSS
    isPrivate := rand.Intn(2)
    switch isPrivate {
    case 1:
        oss = MinioFactory{}.CreateOSS()
    default:
        oss = AliOSSFactory{}.CreateOSS()
    }

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

如上面代码所示,声明了一个抽象工厂,每个产品的创建工厂必须实现抽象工厂的创建方法, 但他们又不耦合于创建前的分支判断,单纯的是创建实例即可, 而分支判断逻辑则被修改到了main方法(客户端), 根据客户端的需求想要哪个产品,就可以跟对应的产品工厂要。

后续如果要添加一个新的oss,我们需要实现产品实力的同时,创建一个相应的创建工厂, 好处是不需要修改/影响现有的其他产品/创建工厂, 而改动的影响被迁移到了客户端中,完美解决了 简单工厂中违反开闭原则的缺点

此处我觉得还符合了依赖倒转的原则,改动被提到了客户端(经常修改的的地方),而底层的方法(产品类、工厂创建类)不受影响,客户端依赖底层抽象,而不是具体的实现

UML

image-20230525235012856.png

优缺点

优点

  1. 符合开闭原则
  2. 创建和使用解耦
  3. 扩展性更好
  4. 继承简单工厂的优点
    1. 单一职责

缺点

可以发现不断地优化解决,附加的设计也越来越多, 之前添加一个实例类型,只需要实现产品接口,再到工厂中加一个分支即可, 现在也需要添加一个工厂类来实现创建, 更复杂了些

本作品采用《CC 协议》,转载必须注明作者和本文链接
我是哈哈怪,每天笑哈哈. go/php开发, 业余喜欢写技术文章,欢迎关注我的个人网站 公众号:哈哈怪玩编程
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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