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

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

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

相关推荐
Kiri霧18 小时前
Rust开发环境搭建
开发语言·后端·rust
间彧19 小时前
Spring事件监听与消息队列(如Kafka)在实现解耦上有何异同?
后端
间彧19 小时前
Java如何自定义事件监听器,有什么应用场景
后端
叶梅树19 小时前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法
间彧19 小时前
CopyOnWriteArrayList详解与SpringBoot项目实战
后端
间彧20 小时前
SpringBoot @FunctionalInterface注解与项目实战
后端
ShareBeHappy_Qin20 小时前
Spring 中使用的设计模式
java·spring·设计模式
程序员小凯20 小时前
Spring Boot性能优化详解
spring boot·后端·性能优化
Asthenia041220 小时前
问题复盘:飞书OAuth登录跨域Cookie方案探索与实践
后端
tuine20 小时前
SpringBoot使用LocalDate接收参数解析问题
java·spring boot·后端