设计模式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()
}	

简单工厂模式实现了对象创建和使用的分离,面向抽象接口编程,但是对工厂类职责过重,每新增一个产品实现,就要修改工厂类的代码,违反了开闭原则。随着产品越来越多,工厂类越来越复杂,所以工厂类适用于产品较少,不会造成工厂方法中的业务逻辑太复杂。

工厂方法模式

一个产品拥有一个工厂,如下图所示

水果(抽象接口) -> 苹果,香蕉等

工厂 (抽象接口)-> 苹果工厂,香蕉工厂等。

image-20230116185643027

实际案例:

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()
}

工厂方法模式,在编写代码中,不需要记住具体类名,只需要面对抽象接口编程。实现了对象创建和使用的分离,系统的可拓展性好,无需修改接口和原类。符合开闭原则。

但是增加了系统中类的个数,增加了系统的抽象性和理解难度。

抽象工厂方法模式

抽象工厂模式在工厂模式的基础上,对工厂进一步抽象。

首先了解一个概念,产品等级结构和产品族。

产品族:水平区分,同类产品

产品等级结构:垂直区分,具有不同特点的同类产品。

image-20230116190644156a

工厂 -> 中国工厂,美国工厂等。

苹果 -> 中国苹果,美国苹果等。

梨 -> 中国梨,美国梨等。

image-20230116192419001

实际案例:

这里产品族是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()
}

单例模式提供了对唯一实例的受控访问,节约系统资源,结构清晰明了,简单易懂。但是因为单例模式没有抽象依赖,所以拓展性较差,除此外也有并发安全的问题。

总结

单例模式,简单工厂,工厂,抽象工厂四个模式中,抽象程度和系统复杂度越来越高,耦合度越来越低,开发者应该根据实际情况选择满足当下系统需求的创造者模式,不能一味追求低耦合度而使得系统庞大复杂,过度设计,不利于系统的维护。

这个系列的帖子