创建型模式

创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用过程分离。

0.1. 单例模式

对于系统中的某些类来说,只有一个实例很重要:

  • 一个系统中存在多个打印任务,但是只能有一个正在工作的任务
  • 一个系统只能有一个窗口管理器或文件系统
  • 一个系统只能有一个计时工具或ID(序号)生成器
  • 一个系统中只能有一份统一的配置文件

如何保证一个类只有一个实例并且这个实例易于被访问呢?

  • 定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象
  • 更好的解决办法是让类自身负责保存它的唯一实例。这个类保证没有其他实例被创建,并且提供一个访问该实例的方法

0.1.1. 定义

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:

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

单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

单例模式只包含一个角色:Singleton(单例)

image

image

0.1.2. 样例

sync.Once是让函数方法只被调用执行一次的实现,最常应用于单例模式,例如:

  • 初始化系统配置
  • 保持数据库唯一连接

这样就可以满足单例模式的要求。

package singleton

import "sync"

//Singleton 是单例模式类
type Singleton struct{}

var (
    singleton *Singleton
    once sync.Once
)

//GetInstance 用于获取单例模式对象
func GetInstance() *Singleton {
	once.Do(func() {
		singleton = &Singleton{}
	})

	return singleton
}

sync.Once内部实现

type Once struct {
    // done indicates whether the action has been performed.
    // It is first in the struct because it is used in the hot path.
    // The hot path is inlined at every call site.
    // Placing done first allows more compact instructions on some architectures (amd64/x86),
    // and fewer instructions (to calculate offset) on other architectures.
    done uint32    // 调用标识符,Once对象初始化时,done值默认为0,Do方法被调用后变为1
    m    Mutex     // 初始化竞态控制,在第一次调用Once.Do()方法时,会通过m加锁,
                    // 保证在第一个Do()方法中的参数f()函数还未执行完毕时,
                    // 其他此时调用Do()方法会被阻塞(不返回也不执行)
}

func (o *Once) Do(f func()) {
    // Do()方法的入参是一个无参数输入与返回的函数

    // Note: Here is an incorrect implementation of Do:
    //
    //  if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    //      f()
    //  }
    //
    // Do guarantees that when it returns, f has finished.
    // This implementation would not implement that guarantee:
    // given two simultaneous calls, the winner of the cas would
    // call f, and the second would return immediately, without
    // waiting for the first's call to f to complete.
    // This is why the slow path falls back to a mutex, and why
    // the atomic.StoreUint32 must be delayed until after f returns.

    // 下面这个if中原子读取o.done的值并判断,是为了保证其他调用会先阻塞,而不是直接退出
    // 如果其他调用直接退出了,但是单例还没有初始化,就会有异常了  
    if atomic.LoadUint32(&o.done) == 0 {    // 当o.done值为0时,执行doSlow()方法
        // Outlined slow-path to allow inlining of the fast-path.
        o.doSlow(f)
    }
    // 当o.done值为1则退出Do()方法
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()  // 加锁
    defer o.m.Unlock()  // 方法退出时释放锁
    if o.done == 0 {    // 再次检查o.done值
        defer atomic.StoreUint32(&o.done, 1)    // 原子操作将o.done值置为1
        f() // 执行传入的函数
    }
}

注意:由于o.m.Lock()处的代码限定,once.Do()内部调用Do()方法时,会造成死锁的发生。

0.1.3. 分析

优点

  1. 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  3. 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点

  1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  3. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。

0.2. 简单工厂模式

0.2.1. 定义

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式包含如下角色:

  • Factory:工厂角色,工厂角色负责实现创建所有实例的内部逻辑
  • Product:抽象产品角色,抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
  • ConcreteProduct:具体产品角色,具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

image

image

0.2.2. 样例

样例代码来源

工厂角色:

package simplefactory

//NewAPI return Api instance by type
func NewAPI(t int) API {
    if t == 1 {
        return &hiAPI{}
    } else if t == 2 {
        return &helloAPI{}
    }
    return nil
}

抽象产品角色:

package simplefactory

//API is interface
type API interface {
    Say(name string) string
}

具体产品角色:

package simplefactory

import "fmt"

//hiAPI is one of API implement
type hiAPI struct{}

//Say hi to name
func (*hiAPI) Say(name string) string {
    return fmt.Sprintf("Hi, %s", name)
}

//HelloAPI is another API implement
type helloAPI struct{}

//Say hello to name
func (*helloAPI) Say(name string) string {
    return fmt.Sprintf("Hello, %s", name)
}

go 语言没有构造函数一说,所以一般会定义NewXXX函数来初始化相关类。 NewXXX 函数返回接口时就是简单工厂模式,也就是说Golang的一般推荐做法就是简单工厂。

在这个simplefactory包中只有API接口和NewAPI函数为包外可见,封装了实现细节。

0.2.3. 分析

简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式包含三个角色:

  • 工厂角色:负责实现创建所有实例的内部逻辑;
  • 抽象产品角色:是所创建的所有对象的父类,负责描述所有实例所共有的公共接口;
  • 具体产品角色:是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。

  • 优点:在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,
  • 缺点:在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。

简单工厂模式适用情况包括:工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。

更详细的介绍看图解设计模式

0.3. 工厂方法模式

0.3.1. 定义

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

工厂方法模式包含如下角色:

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂

image

image

0.3.2. 样例

样例代码来源

抽象产品:

package factorymethod

//Operator 是被封装的实际类接口
type Operator interface {
    SetA(int)
    SetB(int)
    Result() int
}

具体产品:

package factoryMethod

type OperatorBase struct {
    a, b int
}

func (o *OperatorBase) SetA(i int) {
    o.a = i
}

func (o *OperatorBase) SetB(i int) {
    o.b = i
}

//PlusOperator Operator 的实际加法实现
type PlusOperator struct {
    *OperatorBase
}

//Result 获取结果
func (p PlusOperator) Result() int {
    return p.a + p.b
}

//MinusOperator Operator 的实际减法实现
type MinusOperator struct {
    *OperatorBase
}

//Result 获取结果
func (m MinusOperator) Result() int {
    return m.a - m.b
}

抽象工厂:

package factoryMethod

//OperatorFactory 是工厂接口
type OperatorFactory interface {
    Create() Operator
}

具体工厂:

package factoryMethod

//PlusOperatorFactory 是 PlusOperator 的工厂类
type PlusOperatorFactory struct{}

func (PlusOperatorFactory) Create() Operator {
    return &PlusOperator{
        OperatorBase: &OperatorBase{},
    }
}

//MinusOperatorFactory 是 MinusOperator 的工厂类
type MinusOperatorFactory struct{}

func (MinusOperatorFactory) Create() Operator {
    return &MinusOperator{
        OperatorBase: &OperatorBase{},
    }
}

工厂方法模式使用子类的方式延迟生成对象到子类中实现。

Go中不存在继承 所以使用匿名组合来实现

0.3.3. 分析

工厂方法模式又称为工厂模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

工厂方法模式包含四个角色:

  • 抽象产品:定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;
  • 具体产品:实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间往往一一对应;
  • 抽象工厂:声明了工厂方法,用于返回一个产品,它是工厂方法模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;
  • 具体工厂:抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

  • 优点:增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;
  • 缺点:增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。

工厂方法模式适用情况包括:

  1. 一个类不知道它所需要的对象的类;
  2. 一个类通过其子类来指定创建哪个对象;
  3. 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。

更详细的介绍看图解设计模式

0.4. 抽象工厂模式

0.4.1. 定义

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。

抽象工厂模式包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

image

image

0.4.2. 样例

样例代码来源

抽象工厂:

package abstractFactory

// DAOFactory DAO抽象模式工厂接口
type DAOFactory interface {
    CreateOrderMainDAO() OrderMainDAO
    CreateOrderDetailDAO() OrderDetailDAO
}

具体工厂:

package abstractFactory

type RDBDAOFactory struct {
}

func (R *RDBDAOFactory) CreateOrderMainDAO() OrderMainDAO {
    return &RDBMainDAO{}
}

func (R *RDBDAOFactory) CreateOrderDetailDAO() OrderDetailDAO {
    return &RDBDetailDAO{}
}

type XMLDAOFactory struct {
}

func (X *XMLDAOFactory) CreateOrderMainDAO() OrderMainDAO {
    return &XMLMainDAO{}
}

func (X *XMLDAOFactory) CreateOrderDetailDAO() OrderDetailDAO {
    return &XMLDetailDAO{}
}

抽象产品:

package abstractFactory

// OrderMainDAO 为订单主记录
type OrderMainDAO interface {
    SaveOrderMain()
}

// OrderDetailDAO 为订单详情纪录
type OrderDetailDAO interface {
    SaveOrderDetail()
}

具体产品:

package abstractFactory

import "fmt"

// RDBMainDAP 为关系型数据库的OrderMainDAO实现
type RDBMainDAO struct {
}

// SaveOrderMain ...
func (R *RDBMainDAO) SaveOrderMain() {
    fmt.Print("rdb main save\n")
}

// RDBDetailDAO 为关系型数据库的OrderDetailDAO实现
type RDBDetailDAO struct {
}

// SaveOrderDetail ...
func (R *RDBDetailDAO) SaveOrderDetail() {
    fmt.Print("rdb detail save\n")
}

// XMLMainDAO XML存储
type XMLMainDAO struct{}

// SaveOrderMain ...
func (*XMLMainDAO) SaveOrderMain() {
    fmt.Print("xml main save\n")
}

// XMLDetailDAO XML存储
type XMLDetailDAO struct{}

// SaveOrderDetail ...
func (*XMLDetailDAO) SaveOrderDetail() {
    fmt.Print("xml detail save")
}

抽象工厂模式用于生成产品族的工厂,所生成的对象是有关联的。

如果抽象工厂退化成生成的对象无关联则成为工厂函数模式。

比如本例子中使用RDB和XML存储订单信息,抽象工厂分别能生成相关的主订单信息和订单详情信息。 如果业务逻辑中需要替换使用的时候只需要改动工厂函数相关的类就能替换使用不同的存储方式了。

0.4.3. 分析

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。

抽象工厂模式包含四个角色:

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

抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构

  • 优点:隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;
  • 缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。

抽象工厂模式适用情况包括:

  1. 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;
  2. 系统中有多于一个的产品族,而每次只使用其中某一产品族;
  3. 属于同一个产品族的产品将在一起使用;
  4. 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

更详细的介绍看图解设计模式

上次修改: 6 May 2020