【设计模式】什么是工厂模式,有什么优点?

工厂模式是创建型的设计模式。

GoF定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化推迟到其子类。

1.隐藏创建对象的细节,封装创建对象的过程。体现了面向对象编程封装特性

2.调用者不用关心创建对象的细节。体现了单一职责设计原则。

3.非常简洁的代码,实现该对象的创建。体现了 KISS 设计原则。

未用工厂模式前

示例 对接七牛云存储,点击访问官方文档

1.下载sdk go get github.com/qiniu/go-sdk/v7

2.文件上传

go 复制代码
package main

import (
    "context"
    "github.com/qiniu/go-sdk/v7/storagev2/credentials"
    "github.com/qiniu/go-sdk/v7/storagev2/http_client"
    "github.com/qiniu/go-sdk/v7/storagev2/uploader"
)

// localFileUpload 本地文件上传
func localFileUpload(localFile, bucket, domain string) (string, error) {  
    accessKey := "your access key"  
    secretKey := "your secret key"  
    mac := credentials.NewCredentials(accessKey, secretKey)  
  
    key := path.Base(localFile)  
    uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{  
       Options: http_client.Options{  
          Credentials: mac,  
       },  
    })  
  
    err := uploadManager.UploadFile(context.Background(), localFile, &uploader.ObjectOptions{  
       BucketName: bucket,  
       ObjectName: &key,  
       FileName:   key,  
    }, nil)  
    if err != nil {  
       return "", err  
    }  
    if len(domain) != 0 {  
       return fmt.Sprintf("%v/%v", domain, key), nil  
    }  
    return "", nil  
}


// binaryUpload 数据流上传  
func binaryUpload(fileReader io.Reader,fileName, bucket, domain string) (string, error) {  
    accessKey := "your access key"  
    secretKey := "your secret key"  
    mac := credentials.NewCredentials(accessKey, secretKey)  
      
    uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{  
       Options: http_client.Options{  
          Credentials: mac,  
       },  
    })  
  
    err := uploadManager.UploadReader(context.Background(), fileReader, &uploader.ObjectOptions{  
       BucketName: bucket,  
       ObjectName: &fileName,  
       FileName:   fileName,  
    }, nil)  
    if err != nil {  
       return "", err  
    }  
    if len(domain) != 0 {  
       return fmt.Sprintf("%v/%v", domain, fileName), nil  
    }  
    return "", nil  
}

上述这段代码实现了 localFileUpload 本地文件上传和 binaryUpload 数据流上传。

优点:

把对接七牛的功能封装在函数中,体现了封装的思想,让外部调用者不用关心细节。

缺点:

封装的两个函数不好进行后期的维护,比如:

1.需要修改 accessKey 和 secretKey

2.需要新增功能,分片上传

也许你会说,

第一个问题:把 accessKey 和 secretKey 当成参数传入,不就可以了么。

第二个问题:再封装一个分片上传的方法。

的确这样是可以解决,但是还是不够好,请再次看下,函数的内部实现逻辑,是不是觉得有一段代码重复了。

没错就是获取 uploadManager,若需要更改 uploadManager的实现逻辑,是不是每个方法都要修改?

也许你又会说,把获取 uploadManager 在封装一个方法不就解决了。但是若上传文件的流程变化了,获取 uploadManager 后不能马进行文件上传,还需要其他的操作怎么办?

这样又回到刚才的问题,每个方法都需要修改,而且不能很好体现出每一次的步骤。

好吧,说到这里,你可能会不耐烦了,请直接告诉我该怎么做。

用工厂模式后

修改后的代码:

go 复制代码
const timeFormatLayout = "20060102"  
  
type QiNiu struct {  
    accessKey     string  
    secretKey     string  
    uploadManager *uploader.UploadManager  
    opt           QiNiuOption  
}  

// QiNiuOptions QiNiu需要的配置
type QiNiuOptions func(option *QiNiuOption)  
  
type QiNiuOption struct {  
    domain string  
    bucket string  
}  
  
func newQiNiuOption(opts ...QiNiuOptions) QiNiuOption {  
    o := QiNiuOption{}  
    for _, opt := range opts {  
       opt(&o)  
    }  
  
    return o  
}  
  
func WithDomain(domain string) QiNiuOptions {  
    return func(opt *QiNiuOption) {  
       opt.domain = domain  
    }  
}  
  
func WithBucket(bucket string) QiNiuOptions {  
    return func(opt *QiNiuOption) {  
       opt.bucket = bucket  
    }  
}  
  
func NewQiNiu(accessKey, secretKey string, opts ...QiNiuOptions) *QiNiu {  
    opt := newQiNiuOption(opts...)  
    return &QiNiu{  
       accessKey: accessKey,  
       secretKey: secretKey,  
       opt:       opt,  
    }  
}  
  
func (q *QiNiu) GetUploadManager() *QiNiu {  
    mac := credentials.NewCredentials(q.accessKey, q.secretKey)  
    q.uploadManager = uploader.NewUploadManager(&uploader.UploadManagerOptions{  
       Options: http_client.Options{  
          Credentials: mac,  
       },  
    })  
  
    return q  
}  

// UploadLocalFile 本地文件上传  
func (q *QiNiu) UploadLocalFile(filePath string) (string, error) {  
    if len(q.opt.bucket) == 0 {  
       return "", fmt.Errorf("bucket is empty")  
    }  
  
    if q.uploadManager == nil {  
       return "", fmt.Errorf("uploadManager not exist")  
    }  
  
    fileName := path.Base(filePath)  
    fileKey := time.Now().Format(timeFormatLayout) + "/" + fileName  
    err := q.uploadManager.UploadFile(context.Background(), fileName, &uploader.ObjectOptions{  
       BucketName: q.opt.bucket,  
       ObjectName: &fileKey,  
       FileName:   fileKey,  
    }, nil)  
    if err != nil {  
       return "", fmt.Errorf("local file upload error:%v", err)  
    }  
  
    if len(q.opt.domain) != 0 {  
       return q.opt.domain + "/" + fileKey, nil  
    }  
  
    return "", nil  
}  

// UploadBinary 本地文件上传 
func (q *QiNiu) UploadBinary(fileReader io.Reader, fileName string) (string, error) {  
    if len(q.opt.bucket) == 0 {  
       return "", fmt.Errorf("bucket is empty")  
    }  
  
    if q.uploadManager == nil {  
       return "", fmt.Errorf("uploadManager not exist")  
    }  
  
    fileKey := time.Now().Format(timeFormatLayout) + "/" + fileName  
    err := q.uploadManager.UploadReader(context.Background(), fileReader, &uploader.ObjectOptions{  
       BucketName: q.opt.bucket,  
       ObjectName: &fileKey,  
       FileName:   fileKey,  
    }, nil)  
    if err != nil {  
       return "", fmt.Errorf("local file upload error:%v", err)  
    }  
  
    if len(q.opt.domain) != 0 {  
       return q.opt.domain + "/" + fileKey, nil  
    }  
  
    return "", nil  
}  

// QiNiuFactory 使用工厂模式创建 QiNiu
type QiNiuFactory struct{}  
  
func NewQiNiuFactory() *QiNiuFactory {  
    return &QiNiuFactory{}  
}  
  
func (f *QiNiuFactory) Build() *QiNiu {  
    accessKey := "your access key"  
    secretKey := "your secret key" 
    return NewQiNiu(  
       accessKey,  
       secretKey,  
       WithDomain("http://xxx-assets.xxx.com"),  
       WithBucket("xxx-assets"),  
    )  
}

func main() {  
    qiNiu := NewQiNiuFactory().Build()  
  
    // 本地文件上传  
    fileUrl, err := qiNiu.GetUploadManager().UploadLocalFile("./avatar.png")  
    if err != nil {  
       fmt.Println(err)  
    }  
  
    fmt.Println(fileUrl)  
  
    // 数据流上传  
    filePath := "./nginx.jpg"  
    fileReader, err := os.Open(filePath)  
    if err != nil {  
       fmt.Println("file open error", err)  
       return  
    }  
    defer fileReader.Close()  
  
    fileUrl, err = qiNiu.GetUploadManager().UploadBinary(fileReader, path.Base(filePath))  
    if err != nil {  
       fmt.Println(err)  
    }  
  
    fmt.Println(fileUrl)  
}

总结

怎么样?看了上述代码后,有什么感觉,是不是觉得优雅了很多。

1.工厂模式,使用 QiNiuFactory 解决了创建 QiNiu 对象复杂的问题,让外部创建 QiNiu 对象变得简单。 后续若需要多少个 QiNiu 对象,一行代码就解决,并且若需要让 accessKey、secretKey通过其他的方式获取,修改也是非常容易的。

2.获取 uploadManager,使用了链式调用的方式。

后续若需要在获取 uploadManager 后需要增加流程,是不是也很方便。

相关推荐
devlei3 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑5 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3566 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3566 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁6 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp6 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴8 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友8 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒9 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan10 小时前
Go 内存回收-GC 源码1-触发与阶段
后端