掘金 后端 ( ) • 2024-04-28 10:45

theme: smartblue

该系列文章总结常见设计模式的概念、使用场景与Go的实现方案,,但实际上Go语言并不需要刻意地去过度使用设计模式,反而与Go大道至简地思想冲突。

本篇介绍工厂模式(简单工厂、工厂方法、抽象工厂)


简单工厂:

先提出一个场景:

假设现目前有一个水果的类,该类下有一个show方法,不同的水果show的逻辑不同,与之对应的初始化逻辑也不同

//水果类
type Fruit struct {
	//...
	//...
	//...
}

func (f *Fruit) Show(name string) {
	if name == "apple" {
		fmt.Println("apple show")
	} else if name == "banana" {
		fmt.Println("banana show")
	} else if name == "pear" {
		fmt.Println("pear show")
	}
}

//创建一个Fruit对象
func NewFruit(name string) *Fruit {
	fruit := new(Fruit)

	if name == "apple" {
		// newAppleFunc()
	} else if name == "banana" {
		//newBananaFunc()
	} else if name == "pear" {
		//newPearFunc
	}

	return fruit
}

func newAppleFunc(){}
func newBananaFunc()P{}
func newPearFunc(){}

现在看着可能还好,但是不光是show方法还是初始化方法,在每多一个水果时,都需要去方法里面修改,其违反了设计模式中的开闭原则。

其次show方法和NewFruit中代码量会越来越多,职责也会越来越大,导致代码耦合越来越严重,不论是阅读难度还是代码重用都非常的差。

而如果使用工厂模式 在业务方法和类之间添加一个中间层—工厂类,这样做能够带来的好处是:

1.实现类和业务方法之间的解耦,如果类的构造过程发生变更,可以统一收口在工厂类中进行处理,从而对业务方法屏蔽相关细节

2.倘若有多个类都聚拢在工厂类中进行构造,这样各个类的构造流程中就天然形成了一个公共的切面,可以进行一些公共逻辑的执行

例如使用简单工厂对上述代码进行重构:

type Fruit interface {
   show()
}

type Apple struct {
    name string
}


func NewApple(name string) Fruit {
    return &Orange{
        name: name,
    }
}


func (a *Apple) show() {
    fmt.Printf("i am apple: %s", a.name)
}

type Bannana struct {
    name string
}


func NewBannana(name string) Fruit {
    return &Bannana{
        name: name,
    }
}


func (b *Bannana) show() {
    fmt.Printf("i am Bannana : %s",b.name)
}

//============factory========================

type FruitFactory struct {
}


func NewFruitFactory() *FruitFactory {
    return &FruitFactory{}
}


func (f *FruitFactory) CreateFruit(typ string) (Fruit, error) {
    src := rand.NewSource(time.Now().UnixNano())
    rander := rand.New(src)
    name := strconv.Itoa(rander.Int())


    switch typ {
    case "apple":
        return NewApple(name), nil
    case "strawberry":
        return NewBanana(name), nil
    case "cherry":
        return NewCherry(name), nil
    default:
        return nil, fmt.Errorf("fruit typ: %s is not supported yet", typ)
    }
}


简单工厂uml:

image.png

如上述简单工厂的设计模式,业务代码就无序与水果类进行耦合去关心水果类,它只与工厂模块进行依赖;

但是虽然这样设计简单清晰,但是也存在弊端。

例如当每增加一个水果,依然需要去修改createFruit方法的业务逻辑,依然违背了开闭原则,后续水果过多该方法也会因为过多的分支造成复杂度过高。其次对工厂类职责过重,一旦不能工作,系统受到影响

简单工厂总结

综上,简单工厂主要是在类层与业务层中间创建了一个工厂分离对象的创建与使用

大致创建步骤:

  • • 对于拟构造的组件,需要依据其共性,抽离出一个公共 interface
  • • 每个具体的组件类型对 interface 加以实现
  • • 定义一个具体的工厂类,在构造器方法接受具体的组件类型,完成对应类型组件的构造

而优缺点就如我上述所说:

  • 优点:实现了对象创建和使用的分离,简单直观
  • 缺点:组件类扩展时,需要直接修改工厂的组件构造方法,不符合开闭原则,对工厂类职责过重,一旦不能工作,系统受到影响。构造函数复杂度会越来越高

工厂方法:

工厂方法主要是针对简单工厂模式存在的劣势进行的优化。

其不同的地方在于,工厂方法会将水果工厂这个中间人也抽象为一个接口,而creteFruit则为接口下的方法。而每个水果都会有自己的工厂类去实现水果工厂的接口


type FruitFactory interface {
    CreateFruit() Fruit
}


type AppleFactory struct {
}


func NewAppleFactory() FruitFactory {
    return &AppleFactory{}
}


func (a *AppleFactory) CreateFruit() Fruit {
    return NewApple("")
}


type StrawberryFactory struct {
}


func NewStrawberryFactory() FruitFactory {
    return &StrawberryFactory{}
}


func (s *StrawberryFactory) CreateFruit() Fruit {
    return NewStrawberry("")
}


type BananaFactory struct {
}


func NewBananaFactory() FruitFactory {
    return &BananaFactory{}
}


func (b *BananaFactory) BananaFruit() Fruit {
    return NewBanana("")
}

这样去设计工厂类的话,与简单工厂不同的是,你会发现这样的设计模式下,即便后续有频繁扩展水果实现类的需求,也无须对老模块的代码进行修改,而是需要扩展实现一个水果 Fruit 的实现类以及对应的水果工厂实现类即可

简而言之其实工厂方法就是在简单工厂的基础上再对工厂进行了一层抽象。

优点:

  • 系统的可扩展性也就变得非常好,无需修改接口和原类。

  • 对于新产品的创建,符合开闭原则。

缺点:

  • 增加系统中类的个数,复杂度和理解度增加。

  • 增加了系统的抽象性和理解难度。

工厂方法UML:

image.png

抽象工厂模式:

在工厂方法中通过对工厂进行抽象,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。因此,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产。


例如:水果有不同类型:苹果、香蕉。假设只有这三种水果,不会扩展

对应的每个水果都会有自己的一个生产工厂。

这时候如果引入了不同的工厂,例如重庆、四川、北京。而对应的苹果就需要三个不同的工厂(重庆、四川、北京),那使用工厂方法就会导致工厂就会非常多,代码非常繁琐。这种场景就可以使用抽象工厂模式。


抽象工厂方法模式”引出了产品族产品等级结构概念,其目的是为了更加高效的生产同一个产品组产品。

  • 首先,我们把种类相对稳定,不需要频繁扩展变更的维度定义为产品等级. 比如水果中的 Fruit,我们需要固定明确后续 Fruit 只包含Apple 和 Banana 两类,不会进行扩展

  • 针对于种类需要频繁变更的维度,我们将其定义为产品族. 比如上述的产地(例如重庆、四川、北京),后续也许会扩展新疆、海南......

  • 每次需要扩展产品族时,都需要实现对应产品族的工厂 factory 实现类,而无需对老的实现方法直接进行修改,符合开闭原则

  • 针对于不频繁变动的产品等级,如水果中Apple与Banana,每个产地都会有一个具体的 factory 工厂实现类. 其中会统一声明对应于每种水果的构造方法,此时具备实现公共切面的能力

UML如图:

image.png