创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
下面分别来说一下常见的几种创建性设计模式:
一、单例模式
定义
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。
使用场景
从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。除此之外,我们还可以使用单例解决资源访问冲突的问题。
实现
概括起来,要实现一个单例,我们需要关注的点无外乎下面几个:
- 构造函数需要是 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 Transporter interface {
Deliver()
}
type Truck struct {}
func (J Truck) Deliver() {
fmt.Println("this is Truck Deliver")
}
type Ship struct {}
func (Y Ship) Deliver() {
fmt.Println("this is Ship Deliver")
}
func NewTransport(t string) Transporter {
switch t {
case "truck":
return Truck{}
case "ship":
return Ship{}
}
return nil
}
尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(无需频繁的添加类,也没有太多的类)是没有问题的。
三、工厂方法(Factory Method)
定义
简单工厂使用唯一的一个工厂控制着所有产品的实例化,而 工厂方法 中包括一个工厂接口,我们可以动态的实现多种工厂,达到扩展的目的。
工厂方法模式比起简单工厂模式更加符合开闭原则,当我们新增一种类 的时候,只需要新增一个实现了接口的类即可。
使用场景
当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
实现
还是上面的例子,如果公司开了分公司,在北京和广州都有了物流基地,运输途径的地方肯定会不一样,又将如何实现呢?
package factory
//工厂接口
type CompanyInterface interface {
Generate(t string) Transporter
}
//产品接口
type Transporter interface {
Deliver()
}
//具体工厂类
type BjCompany struct {}
type GZCompany struct {}
// 工厂方法
func (bj BjCompany) Generate(t string) Transporter {
switch t {
case "truck":
return Truck{}
case "ship":
return Ship{}
}
return nil
}
func (gz GZCompany) Generate(t string) Transporter {
switch t {
case "truck":
return Truck{}
case "ship":
return Ship{}
}
return nil
}
//具体产品及方法实现
type BJTruck struct {}
func (b BJTruck) Deliver() {
fmt.Println("this is Truck Deliver")
}
type BJShip struct {}
func (b BJShip) Deliver() {
fmt.Println("this is Ship Deliver")
}
type GZTruck struct {}
func (g GZTruck) Deliver() {
fmt.Println("this is Truck Deliver")
}
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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。