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

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

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

相关推荐
AIFQuant32 分钟前
如何通过股票数据 API 计算 RSI、MACD 与移动平均线MA
大数据·后端·python·金融·restful
x70x8039 分钟前
Go中nil的使用
开发语言·后端·golang
REDcker1 小时前
libwebsockets库原理详解
c++·后端·websocket·libwebsockets
源代码•宸2 小时前
Leetcode—47. 全排列 II【中等】
经验分享·后端·算法·leetcode·面试·golang·深度优先
Wyy_9527*2 小时前
行为型设计模式——状态模式
java·spring boot·后端
梅梅绵绵冰2 小时前
springboot初步2
java·spring boot·后端
0和1的舞者4 小时前
公共类的注意事项详细讲解
经验分享·后端·开发·知识·总结
小北方城市网4 小时前
Spring Cloud Gateway 自定义过滤器深度实战:业务埋点、参数校验与响应改写
运维·jvm·数据库·spring boot·后端·mysql
jason.zeng@15022074 小时前
POM构造Spring boot多模块项目
java·spring boot·后端
猿与禅4 小时前
Spring Boot 3.x 集成 Caffeine 缓存框架官方指南
spring boot·后端·缓存·caffeine