工厂模式
回顾简单工厂,我们将创建oss(产品)实例的代码解耦到工厂中进行,实现了业务逻辑和创建的解耦,也避免了代码的重复。但是有没有发现创建的工厂中还是存在了一堆的if/else或是switch判断语句,这些和创建实际是无关的,但是被耦合到工厂里面,若是后面再添加一种oss类型,实现产品的基础上,我们还需要修改工厂,因为他耦合了switch的分支判断, 很明显违反了开闭原则(对扩展是开放的,对修改是关闭的)。
工厂模式解决了什么
而工厂模式正是解决了简单模式中违反开闭原则的缺点,换句话来说 工厂模式 =简单工厂+解决开闭原则
,着重将工厂中判断分支逻辑解耦, 工厂中只剩下创建实例的部分, 那么到底是如何解决的呢,看下根据简单工厂的例子进行的优化:
package main
import (
"fmt"
"io"
"math/rand"
)
// oss 接口指定实现类需要提供哪些能力
type OSS interface {
UploadFile(localFilePath, objectKey string) error
GetFile(objectKey string) (io.Reader, error)
GetSignUrl(objectKey string) (string, error)
}
type AliOSS struct {
EndPoint string // 服务地址
SecretId string // 秘钥id
SecretKey string // 秘钥key
Bucket string // 桶
}
// UploadFile 上传文件到阿里oss
func (AliOSS) UploadFile(localFilePath, objectKey string) error {
fmt.Println("alioss upload file")
return nil
}
// GetFile 下载/获取阿里oss文件
func (AliOSS) GetFile(objectKey string) (io.Reader, error) {
fmt.Println("alioss get file")
return nil, nil
}
// GetSignUrl 获取阿里oss临时访问地址
func (AliOSS) GetSignUrl(objectKey string) (string, error) {
fmt.Println("alioss get sign url")
return "", nil
}
type Minio struct {
EndPoint string // 服务地址
User string // 用户
Password string // 密码
Bucket string // 桶
}
// UploadFile 上传文件到minio
func (Minio) UploadFile(localFilePath, objectKey string) error {
fmt.Println("minio upload file")
return nil
}
// GetFile 下载/获取minio文件
func (Minio) GetFile(objectKey string) (io.Reader, error) {
fmt.Println("minio get file")
return nil, nil
}
// GetSignUrl 获取minio临时访问地址
func (Minio) GetSignUrl(objectKey string) (string, error) {
fmt.Println("minio get sign url")
return "", nil
}
// 生成实例抽象工厂
type Factory interface {
CreateOSS() OSS
}
// 每个产品提供一个创建工厂
type AliOSSFactory struct{}
// CreateOSS 生成alioss 实例的工厂
func (AliOSSFactory) CreateOSS() *AliOSS {
// (省略)获取配置文件中minio的相关配置
return &AliOSS{
EndPoint: "alioss_endpoint",
SecretId: "alioss_secret_id",
SecretKey: "alioss_secret_key",
Bucket: "alioss_env_buckect_name",
}
}
type MinioFactory struct{}
// CreateOSS 生成minio的实例工厂
func (MinioFactory) CreateOSS() *Minio {
// (省略)获取配置文件中minio的相关配置
return &Minio{
EndPoint: "minio_endpoint",
User: "minio_user",
Password: "minio_password",
Bucket: "minio_env_bucket",
}
}
func main() {
// 获取配置文件判断是否私有部署
var oss OSS
isPrivate := rand.Intn(2)
switch isPrivate {
case 1:
oss = MinioFactory{}.CreateOSS()
default:
oss = AliOSSFactory{}.CreateOSS()
}
// 业务逻辑
oss.UploadFile("/tmp/test.txt", "tmp/test.txt")
oss.GetFile("tmp/test.txt")
oss.GetSignUrl("tmp/test.txt")
}
如上面代码所示,声明了一个抽象工厂,每个产品的创建工厂必须实现抽象工厂的创建方法, 但他们又不耦合于创建前的分支判断,单纯的是创建实例即可, 而分支判断逻辑则被修改到了main方法(客户端), 根据客户端的需求想要哪个产品,就可以跟对应的产品工厂要。
后续如果要添加一个新的oss,我们需要实现产品实力的同时,创建一个相应的创建工厂, 好处是不需要修改/影响现有的其他产品/创建工厂, 而改动的影响被迁移到了客户端中,完美解决了 简单工厂中违反开闭原则的缺点
此处我觉得还符合了依赖倒转的原则,改动被提到了客户端(经常修改的的地方),而底层的方法(产品类、工厂创建类)不受影响,客户端依赖底层抽象,而不是具体的实现
UML
优缺点
优点
- 符合开闭原则
- 创建和使用解耦
- 扩展性更好
- 继承简单工厂的优点
- 单一职责
缺点
可以发现不断地优化解决,附加的设计也越来越多, 之前添加一个实例类型,只需要实现产品接口,再到工厂中加一个分支即可, 现在也需要添加一个工厂类来实现创建, 更复杂了些
本作品采用《CC 协议》,转载必须注明作者和本文链接
我是哈哈怪,每天笑哈哈. go/php开发, 业余喜欢写技术文章,欢迎关注我的个人网站 公众号:哈哈怪玩编程