工厂模式是创建型的设计模式。
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 后需要增加流程,是不是也很方便。