工厂模式细分为三种类型:

  • 简单工厂
  • 工厂方法
  • 抽象工厂

简单工厂是工厂方法的简化。

注意,工厂类的模式用来创建产品对象,这里的创建不一定真的是申请内存创建新对象,也可能是维护对象池,由工厂返回空闲对象。

介绍

工厂方法:

  1. 有一个抽象的产品的接口
  2. 若干个产品类以不同方式实现了这个产品接口
  3. 定义一个用来创建产品的工厂接口,包含一个创建抽象产品的方法
  4. 若干个工厂类以不同方式实现这个工厂接口,创建产品的方法返回抽象产品的某种具体实现,工厂各自生产自己的产品。

定义一个用于创建抽象产品对象的接口,让子类决定实例化成哪一个类。 工厂方法使一个类的实例化延迟到其子类。

当增加一个产品时,只需要增加一个工厂接口的子类,满足开闭原则,解决简单工厂生产产品种类过多时导致的内部代码臃肿(switch-case过多)的问题。

  • 标准的工厂方法需要有一个工厂类接口,即UML中的Creator。
    • 这个接口的作用是为了实现里氏替换原则:子类对象能够代替程序中父类对象出现的任何地方,保证代码的优雅。
  • 抽象的产品接口,即UML中的Product,它的存在有三重含义
    • 表明工厂方法创建的产品功能应该是类似的,可以抽象出一个父类
    • 符合里氏替换原则,使代码优雅
    • 符合依赖倒置原则:高层模块不要依赖底层模块。高层模块和高层模块之间进行通信,如Product和Creator。高层模块和低层模块应该通过抽象来互相依赖。

工厂方法

简单工厂

当产品种类固定,后续不会扩展时,可以使用简单工厂模式

  • 工厂类只有一个,没有工厂接口
  • 使用一个工厂类创建各种产品,创建方法返回Product接口
  • 工厂类根据参数决定生产哪种产品

简单工厂模式下,如果后期新增产品,需要修改工厂类,不满足开闭原则。这种情况下就要使用工厂方法了。

简单工厂和工厂方法对比:

  • 简单工厂缺一个工厂接口,只有一个真实的工厂。导致和工厂方法在实现和使用场景上都有区别
  • 实现上:简单工厂直接返回ConcreteProduct对象;工厂方法先获取工厂对象,然后通过工厂对象创建ConcreateProduct
  • 场景上:如果后续会增加Product,就用工厂方法,增加一个工厂类即可;如果Product类别固定,直接使用简单工厂

简单工厂

使用场景

  • 在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
    • 工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。
    • 例如,如果需要向应用中添加一种新产品,只需要开发新的创建者子类,然后重写其工厂方法即可。
  • 如果希望用户能扩展软件库或框架的内部组件,可使用工厂方法。
    • 框架主干逻辑使用工厂接口创建元素对象
    • 当用户需要扩展元素行为时,开发新的工厂来返回拥有新行为的元素
  • 如果希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。
    • 使用工厂方法并不意味着申请内存创建新的对象,还可以复用空闲对象,比如数据库连接、文件系统和网络资源

优缺点

优点

  • 避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  • 开闭原则。无需更改现有客户端代码,就可以在程序中引入新的产品类型(工厂方法模式)。

缺点

  • 需要引入许多新的子类,代码可能会因此变得更复杂。

示例

对于不同场景,需要创建不同的对象,但这些对象的功能很相似,可以抽象成一个父类。

在实际中,很多框架都支持多种配置文件,项目启动时解析配置文件,将文件内容写入到内存中。配置文件格式很多,有xml、json、yaml等,这个时候就需要根据后缀来解析文件,使用工厂模式就很合理。提高了灵活性。

工厂方法是里氏替换原则依赖倒置原则开放-封闭原则的体现。在具体实现上,主要使用了接口与实现的语法。

简单工厂和工厂方法在开放-封闭原则上是一致的

  • 支持增加新的解析器,对于扩展是开放的,不会影响以前的代码
  • 但是需要分别在create方法中加后缀的判断,一定程度上都影响了封闭原则
  • 如果不想违背封闭原则,可以改为用配置文件的方式,提升封闭性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package factory

// ConfigParser 抽象产品
type ConfigParser interface {
	Parse(path string) string
}

// JsonConfigParser 具体产品1
type JsonConfigParser struct {
}

func (*JsonConfigParser) Parse(path string) string {
	return "json config parse"
}

// XmlConfigParser 具体产品2
type XmlConfigParser struct {
}

func (*XmlConfigParser) Parse(path string) string {
	return "xml config parse"
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// ConfigFactory 抽象工厂接口,包含一个创建抽象产品的方法
type ConfigFactory interface {
	CreateParser() ConfigParser
}

// JsonConfigParserFactory 具体工厂1,包含创建具体产品1的方法
type JsonConfigParserFactory struct {
}

func (JsonConfigParserFactory) CreateParser() ConfigParser {
	return &JsonConfigParser{}
}

// XmlConfigParserFactory 具体工厂2
type XmlConfigParserFactory struct {
}

func (x XmlConfigParserFactory) CreateParser() ConfigParser {
	return &XmlConfigParser{}
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// createFactory 工厂方法模式,先获得具体的工厂对象
func createFactory(ext string) ConfigFactory {
	switch ext {
	case "json":
		return &JsonConfigParserFactory{}
	case "xml":
		return &XmlConfigParserFactory{}
	}
	return nil
}

// SimpleFactory 简单工厂模式,直接获取产品对象
type SimpleFactory struct {
}

func (*SimpleFactory) create(ext string) ConfigParser {
	switch ext {
	case "json":
		return &JsonConfigParser{}
	case "xml":
		return &XmlConfigParser{}
	}
	return nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	// 简单工厂,同一个方法支持创建全部产品
	simpleFactory := &SimpleFactory{}
	jsonParser := simpleFactory.create("json")
	if jsonParser != nil {
		jsonParser.Parse("config.json")
	}

	// 工厂方法,先获取相应工厂对象
	xmlFactory := createFactory("xml")
	// 使用工厂对象创建相应产品
	xmlParser := xmlFactory.CreateParser()
	if xmlParser != nil {
		xmlParser.Parse("config.xml")
	}
}

产品种类不会增加用简单工厂,否则用工厂方法。

总结

工厂方法是里氏替换原则、依赖倒转原则、开放-封闭原则的体现。在具体实现上,主要使用了接口与实现的语法。

其实工厂方法提高了灵活性,假设你开发了一个框架,只能解析yaml,代码开源到github上,这时会有很多小伙伴来增强你的代码, 如果解析方面你使用了工厂方法,那么其他人能够很方便的添加新的解析功能,同时对原有逻辑影响降到最低,这就是优雅。

References

guru