跳过正文

设计模式之创建型模式

·727 字·4 分钟
Chuck Chan
作者
Chuck Chan
分享技术、思考与生活

创建型模式
#

1.单例模式
#

单例模式保证知有一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。单例模式的三个要点:

  1. 这个类只能有一个实例
  2. 这个类必须自己创建这个实例
  3. 这个类必须自行向整个系统提供这个实例

下面是一个单例模式的demo

package singelton

import "fmt"

type singelton struct {}

func (*singelton) Foo()  {
	fmt.Println("call singleton.Foo")
}

func (*singelton) Bar()  {
	fmt.Println("call singleton.Bar")
}

var instance = new(singelton)

func GetInstance() *singelton {
	return instance
}

//.... in main.go
func main() {
  s := singelton.GetInstance()
  s.Foo()
  s.Bar()
}

上面这个demo模式展示了单例模式的一种,属于饿汉模式。其含义是在初始化单例唯一指针的时候,就已经提前开辟好了一个对象,申请了内存。饿汉式的好处是,不会出现线程并发创建,导致多个单例的出现,但是缺点是如果这个单例对象在业务逻辑没有被使用,也会客观的创建一块内存对象。那么与之对应的模式叫懒汉模式,代码如下:

package singelton

import "fmt"

type singelton struct {}

func (*singelton) Foo()  {
	fmt.Println("call singleton.Foo")
}

func (*singelton) Bar()  {
	fmt.Println("call singleton.Bar")
}

var instance *singelton

func GetInstance() *singelton {
  //只有首次GetInstance()方法被调用,才会生成这个单例的实例
  if instance == nil { 
    instance = new(singelton)
  }
	return instance
}

上面的懒汉模式的demo有一个明显的问题,就是GetInstance是非线程安全的。要确保线程安全,必须在获取实例的时候加锁。

...

var m sync.Mutex 

func GetInstance() *singelton {
  m.Lock() //互斥锁保护
  defer m.UnLock()
  
  if instance == nil { 
    instance = new(singelton)
  }
	return instance
}

//.... in main.go
func main() {
  s := singelton.GetInstance()
  s.Foo()
  s.Bar()
}

上面代码虽然解决了线程安全,但是每次调用GetInstance()都要加锁会极大影响性能。我们可以考虑使用Once来实现单例模式,Once可以保证instance初始化只执行一次,比起加互斥锁,Once对程序的的性能有极大的提升(Once的具体原理就不讲了,可以看之前的文章 Golang之Once)。

...

var once sync.Once 

func GetInstance() *singelton {
  once.Do(func{
    instance = new(singelton)
  })
	return instance
}

适用场景:

  1. 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  2. 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

2.简单工厂模式
#

如果没有工厂模式,在开发者创建一个类的对象时,如果有很多不同种类的对象,代码会变得相当冗余。

业务逻辑层 —> 基础类模块

为了让业务逻辑层与基础类模块进行接耦,这里可以在中间加一层工厂模块层,来降低业务逻辑层对基础模块层的直接依赖与耦合。

业务逻辑层 —> 工厂模块 —> 基础类模块

简单工厂模式的角色与职责如下:

  • 工厂:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
  • 抽象产品:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品:简单工厂模式所创建的具体实例对象。
package main

import "fmt"

//========抽象产品==========
type Animal interface {
	Call()
}


//========具体产品(基础模块层)==========
type Dog struct {}
func (*Dog) Call()  {
	fmt.Println("i am Dog")
}

type Cat struct {}
func (*Cat) Call()  {
	fmt.Println("i am Cat")
}

type Bird struct {}
func (*Bird) Call()  {
	fmt.Println("i am Bird")
}


//========工厂模块==========
type Factory struct {}

func (*Factory) CreateAnimal(kind string) Animal {
	var animal Animal
	if kind == "dog" {
		animal = new(Dog)
	}else if kind == "cat" {
		animal = new(Cat)
	}else if kind == "bird" {
		animal = new(Bird)
	}
	return animal
}


func main() {
	//========业务逻辑层==========
	factory := new(Factory)

	dog := factory.CreateAnimal("dog")
	dog.Call()

	cat := factory.CreateAnimal("cat")
	cat.Call()

	bird := factory.CreateAnimal("bird")
	bird.Call()
}

优点:

  1. 实现了对象的创建和使用的分离。
  2. 不需要记住具体类名,记住参数即可,减少使用者记忆量。

缺点:

  1. 对工厂类职责过重,一旦不能工作,系统受到影响。
  2. 增加系统中类的个数,复杂度和理解度增加。
  3. 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂。

适用场景:

  1. 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

3.工厂方法模式
#

在前面的简单工厂模式中说到,如果添加新的商品需要修改工厂逻辑,不符合“开闭原则”。为了解决这一缺点,我们可以增加一个抽象工厂角色,这就是工厂方法模式

简单工厂模式 + “开闭原则” = 工厂方法模式

工厂方法模式的角色与职责如下:

  • 抽象工厂:工厂方法模式的核心,任何工厂类都必须实现这个接口。
  • 工厂:具体工厂类是抽象工厂的一个实现,负责实例化产品对象。
  • 抽象产品:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品:简单工厂模式所创建的具体实例对象。
package main

import "fmt"

//========抽象层==========
//抽象工厂
type AbstractFactory interface {
	CreateAnimal() Animal
}

//抽象产品
type Animal interface {
	Call()
}

//========具体产品(基础模块层)==========
type Dog struct {}
func (*Dog) Call()  {
	fmt.Println("i am Dog")
}

type Cat struct {}
func (*Cat) Call()  {
	fmt.Println("i am Cat")
}

type Bird struct {}
func (*Bird) Call()  {
	fmt.Println("i am Bird")
}

type Monkey struct {}
func (*Monkey) Call()  {
	fmt.Println("i am Monkey")
}


//========工厂模块==========
//每种动物都有自己的工厂
type DogFactory struct {
	AbstractFactory
}
func (*DogFactory) CreateAnimal() Animal {
	return new(Dog)
}


type CatFactory struct {
	AbstractFactory
}
func (*CatFactory) CreateAnimal() Animal {
	return new(Cat)
}


type BirdFactory struct {
	AbstractFactory
}
func (*BirdFactory) CreateAnimal() Animal {
	return new(Bird)
}

type MonkeyFactory struct {
	AbstractFactory
}
func (*MonkeyFactory) CreateAnimal() Animal {
	return new(Bird)
}

func main() {
	//========业务逻辑层==========
	dogFactory := new(DogFactory)
	dogFactory.CreateAnimal().Call()

	catFactory := new(CatFactory)
	catFactory.CreateAnimal().Call()

	birdFactory := new(BirdFactory)
	birdFactory.CreateAnimal().Call()

	//重点:新增一个其他类型 monkey 其他工厂并没有任何改动 符合开闭原则思想
	//新增的功能不会影响到之前的已有的系统稳定性
	monkeyFactory := new(MonkeyFactory)
	monkeyFactory.CreateAnimal().Call()
}

优点:

  1. 不需要记住具体类名,甚至连具体参数都不用记忆。
  2. 实现了对象创建和使用的分离。
  3. 系统的可扩展性也就变得非常好,无需修改接口和原类。
  4. 对于新产品的创建,符合开闭原则。

缺点:

  1. 增加系统中类的个数,复杂度和理解度增加。
  2. 增加了系统的抽象性和理解难度。

适用场景:

  1. 客户端不知道它所需要的对象的类。
  2. 抽象工厂类通过其子类来指定创建哪个对象。

4.抽象工厂模式
#

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。因此,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产。工厂方法模式的角色与职责如下:

  • 抽象工厂:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
  • 具体工厂:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
  • 抽象产品:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  • 具体产品:它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。
package main

import "fmt"

//---------抽象层---------

//抽象产品
type AbstractApple interface {
	ShowApple()
}

type AbstractBanana interface {
	ShowBanana()
}

type AbstractPear interface {
	ShowPear()
}


//抽象工厂
type AbstractFactory interface {
	CreateApple() AbstractApple
	CreateBanana() AbstractBanana
	CreatePear() AbstractPear
}

//---------实现层---------

//-----中国产品族-----
type ChinaApple struct {}
func (*ChinaApple) ShowApple()  {
	fmt.Println("中国苹果")
}

type ChinaBanana struct {}
func (*ChinaBanana) ShowBanana()  {
	fmt.Println("中国香蕉")
}

type ChinaPear struct {}
func (*ChinaPear) ShowPear()  {
	fmt.Println("中国梨")
}

//中国工厂
type ChinaFactory struct {}

func (*ChinaFactory)CreateApple() AbstractApple {
	return new(ChinaApple)
}

func (*ChinaFactory)CreateBanana() AbstractBanana {
	return new(ChinaBanana)
}

func (*ChinaFactory)CreatePear() AbstractPear {
	return new(ChinaPear)
}

//-----日本产品族-------
type JapanApple struct {}
func (*JapanApple) ShowApple()  {
	fmt.Println("日本苹果")
}

type JapanBanana struct {}
func (*JapanBanana) ShowBanana()  {
	fmt.Println("日本香蕉")
}

type JapanPear struct {}
func (*JapanPear) ShowPear()  {
	fmt.Println("日本梨")
}

//日本工厂
type JapanFactory struct {}

func (*JapanFactory)CreateApple() AbstractApple {
	return new(JapanApple)
}

func (*JapanFactory)CreateBanana() AbstractBanana {
	return new(JapanBanana)
}

func (*JapanFactory)CreatePear() AbstractPear {
	return new(JapanPear)
}

//-----美国产品族-------
type AmericanApple struct {}
func (*AmericanApple) ShowApple()  {
	fmt.Println("美国苹果")
}

type AmericanBanana struct {}
func (*AmericanBanana) ShowBanana()  {
	fmt.Println("美国香蕉")
}

type AmericanPear struct {}
func (*AmericanPear) ShowPear()  {
	fmt.Println("美国梨")
}

//美国工厂
type AmericanFactory struct {}

func (*AmericanFactory)CreateApple() AbstractApple {
	return new(AmericanApple)
}

func (*AmericanFactory)CreateBanana() AbstractBanana {
	return new(AmericanBanana)
}

func (*AmericanFactory)CreatePear() AbstractPear {
	return new(AmericanPear)
}

//-------业务逻辑层------
func main()  {
	//需求1:需要美国的苹果、香蕉、梨
	americanFactory := new(AmericanFactory)
	apple := americanFactory.CreateApple()
	banana := americanFactory.CreateBanana()
	pear := americanFactory.CreatePear()
	apple.ShowApple()
	banana.ShowBanana()
	pear.ShowPear()


	//需求2:需要中国的苹果、香蕉
	chineseFactory := new(ChinaFactory)
	apple = chineseFactory.CreateApple()
	banana = chineseFactory.CreateBanana()
	apple.ShowApple()
	banana.ShowBanana()
}

优点:

  1. 拥有工厂方法模式的优点。
  2. 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  3. 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点:

  1. 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

适用场景:

  1. 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
  2. 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。