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

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

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 后需要增加流程,是不是也很方便。

相关推荐
Asthenia041243 分钟前
为什么说MVCC无法彻底解决幻读的问题?
后端
Asthenia041244 分钟前
面试官问我:三级缓存可以解决循环依赖的问题,那两级缓存可以解决Spring的循环依赖问题么?是不是无法解决代理对象的问题?
后端
Asthenia04121 小时前
面试复盘:使用 perf top 和火焰图分析程序 CPU 占用率过高
后端
Asthenia04121 小时前
面试复盘:varchar vs char 以及 InnoDB 表大小的性能分析
后端
Asthenia04121 小时前
面试问题解析:InnoDB中NULL值是如何记录和存储的?
后端
Asthenia04121 小时前
面试官问我:TCP发送到IP存在但端口不存在的报文会发生什么?
后端
Asthenia04121 小时前
HTTP 相比 TCP 的好处是什么?
后端
Asthenia04121 小时前
MySQL count(*) 哪个存储引擎更快?为什么 MyISAM 更快?
后端
Asthenia04121 小时前
面试官问我:UDP发送到IP存在但端口不存在的报文会发生什么?
后端
Asthenia04121 小时前
深入理解 TCP backlog 参数:意义、应用场景与 Netty 中的应用
后端