头图
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。

下面分别来说一下常见的几种创建性设计模式:

一、单例模式

定义

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。

使用场景

从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。除此之外,我们还可以使用单例解决资源访问冲突的问题。

实现

概括起来,要实现一个单例,我们需要关注的点无外乎下面几个:

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  • 考虑对象创建时的线程安全问题;
  • 考虑是否支持延迟加载;
  • 考虑 getInstance() 性能是否高(是否加锁)。

根据是否支持延迟加载,单例模式分为饿汉式和懒汉式
1. 饿汉式
在类加载的时候,instance 静态实例就已经创建并初始化好 了,所以,instance 实例的创建过程是线程安全的。

package singleton

type Singleton struct{}

var singleton *Singleton

func init() {
    singleton = &Singleton{}
}

// GetInstance 获取实例
func GetInstance() *Singleton {
    return singleton
}

2. 懒汉式(双重检测)
懒汉式相对于饿汉式的优势是支持延迟加载,但是因为需要加锁,有性能问题。

双重检测:在懒汉式的实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再进入到加锁逻辑中,这样便既支持延迟加载、又支持高并发了。
package singleton

import "sync"

var (
    lazySingleton *Singleton
    once          = &sync.Once{}
)

// GetLazyInstance 懒汉式
func GetLazyInstance() *Singleton {
    if lazySingleton == nil {
        once.Do(func() {
            lazySingleton = &Singleton{}
        })
    }
    return lazySingleton
}

饿汉式将耗时的初始化操作,提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。

如果实例占用资源多,按照 fail-fast 的设计原则(有问题及早暴露),那我们也希望在程序启动时就将这个实例初始化好。如果资源不够,我们可以立即去修复。

综合来说,饿汉式还是比支持双重检测的懒汉式更推荐使用。

一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。

在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。

而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。

二、简单工厂(Simple Factory)

定义

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。

使用场景

例如,一个物流公司,有陆运、海运和空运几种运输模式,这里的实现如下:声明一个Transport接口,它有一个叫deliver交付的方法,卡车Truck类和轮船Ship类都实现该接口,每个类都将以不同的方式实现该方法,卡车走陆路交付货物, 轮船走海路交付货物。工厂类可以根据传进的参数实例化相应的类,调用deliver方法都能完成运输的任务。

实现

package factory

type Trans­porter interface {
    Deliv­er()
}

type Truck struct {}

func (J Truck) Deliv­er() {
    fmt.Println("this is Truck Deliv­er")
}

type Ship struct {}


func (Y Ship) Deliv­er() {
    fmt.Println("this is Ship Deliv­er")
}


func NewTrans­port(t string) Trans­porter {
    switch t {
    case "truck":
        return Truck{}
    case "ship":
        return Ship{}
    }
    return nil
}

尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(无需频繁的添加类,也没有太多的类)是没有问题的。

三、工厂方法(Factory Method)

定义

简单工厂使用唯一的一个工厂控制着所有产品的实例化,而 工厂方法 中包括一个工厂接口,我们可以动态的实现多种工厂,达到扩展的目的。
工厂方法模式比起简单工厂模式更加符合开闭原则,当我们新增一种类 的时候,只需要新增一个实现了接口的类即可。

使用场景

当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。

实现

还是上面的例子,如果公司开了分公司,在北京和广州都有了物流基地,运输途径的地方肯定会不一样,又将如何实现呢?

package factory

//工厂接口
type CompanyInterface interface {
    Generate(t string) Trans­porter
}

//产品接口
type Trans­porter interface {
    Deliv­er()
}

//具体工厂类
type BjCompany struct {}

type GZCompany  struct {}


// 工厂方法
func (bj BjCompany) Generate(t string) Trans­porter {
    switch t {
    case "truck":
        return Truck{}
    case "ship":
        return Ship{}
    }
    return nil
}

func (gz GZCompany) Generate(t string) Trans­porter {
    switch t {
    case "truck":
        return Truck{}
    case "ship":
        return Ship{}
    }
    return nil
}

//具体产品及方法实现
type BJTruck struct {}

func (b BJTruck) Deliver() {
    fmt.Println("this is Truck Deliv­er")
}

type BJShip struct {}

func (b BJShip) Deliver() {
    fmt.Println("this is Ship Deliv­er")
}

type GZTruck struct {}

func (g GZTruck) Deliver() {
    fmt.Println("this is Truck Deliv­er")
}

type GZShip struct {}

func (g GZShip) Deliver() {
    fmt.Println("this is Ship Deliver")
}

简单工厂模式和工厂方法模式看起来很相似,本质区别就在于,如果在物流公司中直接创建运输产品,是依赖具体某个分公司的,扩展性、弹性、可维护性都较差,而如果将实例化的代码抽象出来,不再依赖具体的公司,而是依赖于抽象的公司接口,使对象的实现从使用中解耦,这样就拥有很强的扩展性了,也可以称为 『依赖倒置原则』

四、抽象工厂(Abstract Factory)

定义

抽象工厂模式就是我们的抽象工厂约定了可以生产的产品,这些产品都包含多种规格,然后我们可以从抽象工厂为每一种规格派生出具体工厂类,然后让这些具体工厂类生产具体的产品。
我们可以让一个工厂负责创建多个不同类型的对象,而不是只创建一种 对象。这样就可以有效地减少工厂类的个数。

使用场景

默认的规则配置解析器类只会根据配置文件格式(Json、Xml、Yaml......)来分类,但是,如果类有两种分类方 式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会这有8 个 parser 类。

针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。

抽象工厂就是针对这种非常特殊的场景而诞生的。

package factory

// IRuleConfigParser IRuleConfigParser
type IRuleConfigParser interface {
    Parse(data []byte)
}

// jsonRuleConfigParser jsonRuleConfigParser
type jsonRuleConfigParser struct{}

// Parse Parse
func (j jsonRuleConfigParser) Parse(data []byte) {
    panic("implement me")
}

// ISystemConfigParser ISystemConfigParser
type ISystemConfigParser interface {
    ParseSystem(data []byte)
}

// jsonSystemConfigParser jsonSystemConfigParser
type jsonSystemConfigParser struct{}

// Parse Parse
func (j jsonSystemConfigParser) ParseSystem(data []byte) {
    panic("implement me")
}

// IConfigParserFactory 工厂方法接口
type IConfigParserFactory interface {
    CreateRuleParser() IRuleConfigParser
    CreateSystemParser() ISystemConfigParser
}

type jsonConfigParserFactory struct{}

func (j jsonConfigParserFactory) CreateRuleParser() IRuleConfigParser {
    return jsonRuleConfigParser{}
}

func (j jsonConfigParserFactory) CreateSystemParser() ISystemConfigParser {
    return jsonSystemConfigParser{}
}

五、建造者模式

定义

建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。

使用场景

  • 参数很多
  • 参数包含必选参数和可选参数
  • 配置项之间有一定的依赖关系
  • 创建的对象是不可变对象

上述的情况直接使用构造函数设置必填项,通过 set() 方法设置可选配置项就无法满足了

示例

顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。
对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。

实现

详见另一篇博文:使用建造者模式封装go的http库

参考资料:
1、《设计模式之美》
2、https://refactoringguru.cn/de...
3、https://lailin.xyz/post/go-de...

爆裂Gopher
20 声望11 粉丝

一篇文章讲明白一个知识点,每月更新,欢迎关注与交流。