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

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

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

相关推荐
风象南4 分钟前
我把大脑开源给了AI
人工智能·后端
橙序员小站5 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德5 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆6 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
开心就好20258 小时前
UniApp开发应用多平台上架全流程:H5小程序iOS和Android
后端·ios
悟空码字8 小时前
告别“屎山代码”:AI 代码整洁器让老项目重获新生
后端·aigc·ai编程
小码哥_常8 小时前
大厂不宠@Transactional,背后藏着啥秘密?
后端
奋斗小强8 小时前
内存危机突围战:从原理辨析到线上实战,彻底搞懂 OOM 与内存泄漏
后端
小码哥_常9 小时前
Spring Boot接口防抖秘籍:告别“手抖”,守护数据一致性
后端
心之语歌9 小时前
基于注解+拦截器的API动态路由实现方案
java·后端