设计模式2-创造型模式及应用
本文中代码案例简化自我的个人项目LightCD。LightCD是基于k8s的轻量级cicd系统,工作模式为master&slave模式,slave构建镜像引擎为img(buildkit)和docker。
模式名称 | 介绍 |
---|---|
简单工厂模式 | 专门定义一个类来创建其他类的实例。 |
工厂模式 | 定义一个工厂接口,每个类有自己的类工厂,类工厂实现工厂接口。将创建实例类的功能下放到类工厂中。 |
抽象工厂模式 | 提供一个创建一系列相关或者相互依赖的接口,而无需指定它们具体的类。 |
单例模式 | 是保证一个类仅有一个实例,并提供一个访问它的全局访问点。 |
简单工厂模式
// -----抽象层-----
// 构建引擎接口
type Engine interface {
Build()
Push()
Clean()
}
// -----实现层-----
// Docker引擎,实现Engine接口
type EDocker struct {}
func (d *EDocker)Build() {}
func (d *EDocker)Push() {}
func (d *EDocker)Clean() {}
// Img引擎,实现Engine接口
type EImg struct {}
func (i *EImg)Build() {}
func (i *EImg)Push() {}
func (i *EImg)Clean() {}
// -----工厂模块-----
// 简单工厂
type SimpleFactory struct {}
// 创造引擎
func (s *SimpleFactory) NewEngine(name string) Engine{
if name == "docker" {
return &EDocker{}
} else if name == "img" {
return &EImg{}
}
return &EDocker{}
}
// -----业务逻辑层-----
func main() {
// 初始化一个工厂
f := SimpleFactory{}
// 工厂造引擎
engine := f.NewEngine("img")
// 拿到引擎
engine.build()
}
简单工厂模式实现了对象创建和使用的分离,面向抽象接口编程,但是对工厂类职责过重,每新增一个产品实现,就要修改工厂类的代码,违反了开闭原则。随着产品越来越多,工厂类越来越复杂,所以工厂类适用于产品较少,不会造成工厂方法中的业务逻辑太复杂。
工厂方法模式
一个产品拥有一个工厂,如下图所示
水果(抽象接口) -> 苹果,香蕉等
工厂 (抽象接口)-> 苹果工厂,香蕉工厂等。
实际案例:
package main
type Engine interface {
Build()
Push()
Clean()
}
type AbstractFactory interface {
CreateEngine() Engine
}
// -----实现层-----
// Docker引擎,实现Engine接口
type EDocker struct{}
func (d *EDocker) Build() {}
func (d *EDocker) Push() {}
func (d *EDocker) Clean() {}
// Img引擎,实现Engine接口
type EImg struct{}
func (i *EImg) Build() {}
func (i *EImg) Push() {}
func (i *EImg) Clean() {}
// -----工厂模块-----
// Docker引擎工厂
type EDockerFactory struct {}
// 实现抽象工厂接口
func (s *EDockerFactory) CreateEngine() Engine {
return &EDocker{}
}
// Img引擎工厂
type EImgFactory struct{}
// 实现抽象工厂接口
func (s *EImgFactory) CreateEngine() Engine {
return &EImg{}
}
// -----业务逻辑层-----
func main() {
// 初始化抽象工厂
var DockerFac AbstractFactory
// 选择一个工厂
DockerFac = &EDockerFactory{}
d := DockerFac.CreateEngine()
d.Build()
var ImgFac AbstractFactory
// 选择一个工厂
ImgFac = &EImgFactory{}
i := ImgFac.CreateEngine()
i.Build()
}
工厂方法模式,在编写代码中,不需要记住具体类名,只需要面对抽象接口编程。实现了对象创建和使用的分离,系统的可拓展性好,无需修改接口和原类。符合开闭原则。
但是增加了系统中类的个数,增加了系统的抽象性和理解难度。
抽象工厂方法模式
抽象工厂模式在工厂模式的基础上,对工厂进一步抽象。
首先了解一个概念,产品等级结构和产品族。
产品族:水平区分,同类产品
产品等级结构:垂直区分,具有不同特点的同类产品。
工厂 -> 中国工厂,美国工厂等。
苹果 -> 中国苹果,美国苹果等。
梨 -> 中国梨,美国梨等。
实际案例:
这里产品族是Img引擎,Docker引擎。产品等级结构是测试版本,公开版本
package main
// -----抽象层-----
type AbstractEDocker interface {
Build()
Push()
Clean()
}
type AbstractEImg interface {
Build()
Push()
Clean()
}
type AbstractFactory interface {
CreateDockerEngine() AbstractEDocker
CreateImgEngine() AbstractEImg
}
// -----实现层-----
// test版本Docker引擎,实现Engine接口
type TestEDocker struct{}
func (d *TestEDocker) Build() {}
func (d *TestEDocker) Push() {}
func (d *TestEDocker) Clean() {}
// test版本Img引擎,实现Engine接口
type TestEImg struct{}
func (i *TestEImg) Build() {}
func (i *TestEImg) Push() {}
func (i *TestEImg) Clean() {}
// -----工厂模块-----
// test版本工厂
type TestVersionFactory struct{}
func (f *TestVersionFactory) CreateDockerEngine() AbstractEDocker {
return &TestEDocker{}
}
func (f *TestVersionFactory) CreateImgEngine() AbstractEImg {
return &TestEImg{}
}
// release版本Docker引擎,实现Engine接口
type ReleaseEDocker struct{}
func (d *ReleaseEDocker) Build() {}
func (d *ReleaseEDocker) Push() {}
func (d *ReleaseEDocker) Clean() {}
// test版本Img引擎,实现Engine接口
type ReleaseEImg struct{}
func (i *ReleaseEImg) Build() {}
func (i *ReleaseEImg) Push() {}
func (i *ReleaseEImg) Clean() {}
// release版本工厂
type ReleaseVersionFactory struct{}
func (f *ReleaseVersionFactory) CreateDockerEngine() AbstractEDocker {
return &ReleaseEDocker{}
}
func (f *ReleaseVersionFactory) CreateImgEngine() AbstractEImg {
return &ReleaseEImg{}
}
// -----业务逻辑层-----
func main() {
// 初始化抽象工厂
var Fac AbstractFactory
// 创建测试版本的工厂
Fac = &TestVersionFactory{}
// 创建一个测试版本的Img引擎
ti := Fac.CreateImgEngine()
ti.Build()
// 创建一个测试版本的docker引擎
td := Fac.CreateDockerEngine()
td.Build()
// 创建公开版本的工厂
Fac = &ReleaseVersionFactory{}
ri := Fac.CreateImgEngine()
ri.Build()
rd := Fac.CreateDockerEngine()
rd.Build()
}
抽象工厂模式拥有工厂模式的优点,增加产品族(水平拓展)很方便,无需修改已有代码。
但是也有缺点。首先是系统复杂度增加,由“一纬“到”二维“。增加产品等级结构时,需要对原有系统进行较大改动,比如上述例子中,当我再增加一个Stage版本时,需要再增加两个实现类StageImg和StageDocker,这些实现类需要实现各自的接口,除此外,还需要增加一个Stage版本工厂实现类,实现自己的接口。显然带来较大的不便,违背了“开闭原则”。
单例模式
单例模式较为简单,这里直接贴代码例子
package main
import "fmt"
/*
三个要点:
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
*/
/*
保证一个类永远只能有一个对象
*/
//1、保证这个类非公有化,外界不能通过这个类直接创建一个对象
// 那么这个类就应该变得非公有访问 类名称首字母要小写
type singelton struct {}
//2、但是还要有一个指针可以指向这个唯一对象,但是这个指针永远不能改变方向
// Golang中没有常指针概念,所以只能通过将这个指针私有化不让外部模块访问
var instance *singelton = new(singelton)
//3、如果全部为私有化,那么外部模块将永远无法访问到这个类和对象,
// 所以需要对外提供一个方法来获取这个唯一实例对象
// 注意:这个方法是否可以定义为singelton的一个成员方法呢?
// 答案是不能,因为如果为成员方法就必须要先访问对象、再访问函数
// 但是类和对象目前都已经私有化,外界无法访问,所以这个方法一定是一个全局普通函数
func GetInstance() *singelton {
return instance
}
func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}
func main() {
s := GetInstance()
s.SomeThing()
}
单例模式提供了对唯一实例的受控访问,节约系统资源,结构清晰明了,简单易懂。但是因为单例模式没有抽象依赖,所以拓展性较差,除此外也有并发安全的问题。
总结
单例模式,简单工厂,工厂,抽象工厂四个模式中,抽象程度和系统复杂度越来越高,耦合度越来越低,开发者应该根据实际情况选择满足当下系统需求的创造者模式,不能一味追求低耦合度而使得系统庞大复杂,过度设计,不利于系统的维护。