创建型模式

寒江蓑笠翁大约 17 分钟设计模式设计模式go

创建型模式

创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。 这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

简单工厂模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

在Go中是没有构造函数的说法,一般会定义Newxxxx函数来初始化相关的结构体或接口,而通过Newxxx函数来初始化返回接口时就是简单工厂模式,一般对于Go而言,最推荐的做法就是简单工厂。

// Creature 生物接口
type Creature interface {
   run()
}

// Human 实现了生物接口
type Human struct {
}

func (h Human) run() {

}

// Animal 实现了生物接口
type Animal struct {
}

func (a Animal) run() {

}

// 这就是简单工厂
func NewCreature(types string) Creature {
   // 创建逻辑
   if len(types) == 0 {
      return &Human{}
   }
   return &Animal{}
}

对于Go而言,工厂模式显得不那么重要,因为Go并不像Java万物都需要new出来,也并不需要一个专门的接口或者结构体来统一管理创建对象,并且Go的调用是基于包而不是结构体或者接口。

优点:封装了创建的逻辑

缺点:每新增一个生物的实现,就要修改一次创建逻辑

工厂方法模式

工厂方法的区别在于,简单工厂是直接创建对象并返回,而工厂模式只定义一个接口,将创建的逻辑交给其子类来实现,即将创建的逻辑延迟到子类。

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
// Creature 抽象生物接口
type Creature interface {
	run()
}

// Human 实现了生物接口
type Human struct {
}

func (h Human) run() {
	fmt.Println("人类在跑")
}

// Animal 实现了生物接口
type Animal struct {
}

func (a Animal) run() {
	fmt.Println("动物在跑")
}

// 生物抽象工厂
type CreatureFactory interface {
	creature() Creature
}

// 实现生物工厂的人类具体工厂
type HumanFactory struct {
}


func (h HumanFactory) creature() Creature {
	return &Human{}
}

// 实现生物工厂的动物具体工厂
type AnimalFactory struct {
}

func (a AnimalFactory) creature() Creature {
	return &Animal{}
}

func TestFactory(t *testing.T) {
	var factory CreatureFactory
	factory = HumanFactory{}
	factory.creature().run()
	factory = AnimalFactory{}
	factory.creature().run()
}

输出

人类在跑
动物在跑

优点:封装了创建逻辑,将创建逻辑延迟到子类

缺点:新增一个生物实现时不需要再修改原有的逻辑,但需要新增一个对应的工厂实现。

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

接下来创建一个职业接口,有工匠和士兵两个职业,人分为亚洲人,欧洲人。倘若继续使用工厂方法模式,就需要给人创建一个抽象工厂,再分别创建两个人种创建具体工厂,职业也是类似,一个工厂只能创建同一类的实体,这样做会导致代码量大幅度增加。抽象工厂就是为了解决这个问题而生的。

// 工人
type Worker interface {
   Work()
}

// 亚洲工人
type AsianWorker struct {
}

func (a AsianWorker) Work() {
   fmt.Println("Asian worker  work")
}

// 欧洲工人
type EuropeanWorker struct {
}

func (e EuropeanWorker) Work() {
   fmt.Println("European worker  work")
}

// 士兵
type Solder interface {
   Attack()
}

// 亚洲士兵
type AsianSolder struct {
}

func (a AsianSolder) Attack() {
   fmt.Println("AsianSolder attack")
}

// 欧洲士兵
type EuropeanSolder struct {
}

func (e EuropeanSolder) Attack() {
   fmt.Println("EuropeanSolder attack")
}

// 人类抽象工厂
type HumansFactory interface {
   CreateWorker() Worker
   CreateSolder() Solder
}

// 亚洲工厂
type AsiansFactory struct {
}

func (a AsiansFactory) CreateWorker() Worker {
   return AsianWorker{}
}
func (a AsiansFactory) CreateSolder() Solder {
   return AsianSolder{}
}

// 欧洲工厂
type EuropeanFactory struct {
}

func (e EuropeanFactory) CreateWorker() Worker {
   return EuropeanWorker{}
}
func (e EuropeanFactory) CreateSolder() Solder {
   return EuropeanSolder{}
}

优点:当更换一套适用的规则时,例如将全部亚洲人换成欧洲人,可以做到无缝更换,只需要换一个工厂即可,不会有任何影响。

缺点:当内部出现变化的话,几乎所有工厂都要做出对应的变化,例如新增一个人种或新增一个职业。

建造者模式

建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。

具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。

产品类(Product):要创建的复杂对象。

指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

下面以造汽车为例子,汽车需要安装引擎,轮胎,地盘,车架。需要一个汽车建造者接口,和两个实现,分别是卡车建造者和公交车建造者,最后是一个指挥者。

type Car struct {
   engine  string
   wheel   string
   chassis string
   frame   string
}

func (c *Car) SetEngine(engine string) {
   c.engine = engine
}

func (c *Car) SetWheel(wheel string) {
   c.wheel = wheel
}

func (c *Car) SetChassis(chassis string) {
   c.chassis = chassis
}

func (c *Car) SetFrame(frame string) {
   c.frame = frame
}

func (c *Car) String() string {
   return fmt.Sprintf("%s %s %s %s", c.engine, c.wheel, c.chassis, c.frame)
}

// 汽车建造者
type CarBuilder interface {
   BuildEngine()
   BuildWheel()
   BuildChassis()
   BuildFrame()
   Build() *Car
}

//指挥者
type CarDirector struct {
   builder *CarBuilder
}

func NewCarDirector(builder CarBuilder) *CarDirector {
   return &CarDirector{builder: &builder}
}

func (d *CarDirector) Build() *Car {
   (*d.builder).BuildEngine()
   (*d.builder).BuildWheel()
   (*d.builder).BuildChassis()
   (*d.builder).BuildFrame()
   return (*d.builder).Build()
}

// 卡车建造者
type TruckBuilder struct {
   truck *Car
}

func NewTruckBuilder() *TruckBuilder {
   return &TruckBuilder{truck: &Car{}}
}

func (t *TruckBuilder) BuildEngine() {
   t.truck.SetEngine("卡车引擎")
}

func (t *TruckBuilder) BuildWheel() {
   t.truck.SetWheel("卡车轮胎")
}

func (t *TruckBuilder) BuildChassis() {
   t.truck.SetChassis("卡车底盘")
}

func (t *TruckBuilder) BuildFrame() {
   t.truck.SetFrame("卡车车架")
}

func (t *TruckBuilder) Build() *Car {
   return t.truck
}

// 公交车建造者
type BusBuilder struct {
   bus *Car
}

func NewBusBuilder() *BusBuilder {
   return &BusBuilder{bus: &Car{}}
}

func (b *BusBuilder) BuildEngine() {
   b.bus.SetEngine("巴士引擎")
}

func (b *BusBuilder) BuildWheel() {
   b.bus.SetWheel("巴士轮胎")
}

func (b *BusBuilder) BuildChassis() {
   b.bus.SetChassis("巴士底盘")
}

func (b *BusBuilder) BuildFrame() {
   b.bus.SetFrame("巴士车架")
}

func (b *BusBuilder) Build() *Car {
   return b.bus
}

func TestBuilder(t *testing.T) {
   director := NewCarDirector(NewTruckBuilder())
   fmt.Println(director.Build().String())
   director = NewCarDirector(NewBusBuilder())
   fmt.Println(director.Build().String())
}

输出

卡车引擎 卡车轮胎 卡车底盘 卡车车架
巴士引擎 巴士轮胎 巴士底盘 巴士车架

一般建造者模式在部件构建顺序和次序不太复杂的时候,都会选择将指挥者嵌入建造者中,本例选择分离了出来,比较符合单一职责原则,并且可以修改一下逻辑,改为链式调用会更好些。

优点:建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。

缺点:造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。

具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。

访问类:使用具体原型类中的 clone() 方法来复制新的对象。

type Cloneable interface {
   Clone() Cloneable
}

type Person struct {
   name string
}

func (p Person) Clone() Cloneable {
   return Person{p.name}
}

func TestClone(t *testing.T) {
   person := Person{"jack"}
   person1 := person.Clone().(Person)
   fmt.Println(person1)
}

提示

在Go中深克隆一个对象并不属于设计模式的范畴

单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

type Singleton struct {
}

var singleton *Singleton

var once sync.Once

// 懒汉方式 双重检查锁 线程安全
func Instance() *Singleton {
   once.Do(func() {
      singleton = &Singleton{}
   })
   return singleton
}

// 饿汉方式 init加载
func init() {
   singleton = &Singleton{}
}

懒汉方式是延迟加载,即被访问时才加载,饿汉方式是当包加载时该单例就被加载。

上次编辑于:
贡献者: 246859