跳到主要内容

工厂方法模式 Factory Method Pattern

概念

工厂方法模式,简称为工厂模式,也被称为虚拟构造器(虚拟构造器)或多态工厂(多态工厂)。它是一种类创建模式。在工厂方法模式中,工厂的父类负责定义创建产品对象的通用接口,而工厂子类则负责生成具体的产品对象。这种方法的目的是将产品类的实例化推迟到工厂子类。这意味着由工厂子类决定实例化哪个具体的产品类。

  • 如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
  • 将 new 操作简单封装,遇到 new 的时候就应该考虑是否用工厂模式;
  • 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性

根据不同的输入返回不同类的实例
将对象的创建和实现分离

目的

为了避免简单工厂的缺点,工厂方法模式定义了一个创建对象的接口 但是动态地把类型选择交给了子类,创建过程被推迟 deferred 在了子类

在简单工厂中,当增加一个新产品类时,工厂类里需要增加新产品类的代码,工厂类逻辑发生改变; 工厂方法模式,将工厂作为一个抽象类(依赖倒置原则),抽象工厂可以实例化成生产具体某一类产品的扩展工厂,扩展工厂才专门用来生产产品

上图不是 abstract,而是 interface

模式结构

  • 产品: 抽象产品,定义了工厂方法所创建对象的接口
  • 扩展工厂: 实现工厂接口
  • 工厂: 抽象工厂
  • 扩展工厂: override 抽象工厂的方法,用于生产扩展产品对象

常见案例

在 Java 中,可以利用反射来创建对象或者修改产品的名字

// 在运行时创建一个字符串类型的对象的模版代码(注意这里String可以是一个变量)
Class c = Class.forName("String")
Object obj = c.newInstance()
return obj

例子:Java 中通过配置文件创建工厂对象,再创建产品对象

Configuration File

<?xml version="1.0"?>
<config>
<className>NameOfFactory</className>
</config>

Tool class XMLUtil

创建文档对象,然后绑定 Xml,读取其中标签的名字,通过类名生成实例对象 实现创建配置文档中的自定义类

// 创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance()
DocumentBuilder builder = dFactory.newDocumentBuilder()

Document doc
doc = builder.parse(new File("config.xml"))

// 获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className")
Node classNode = nl.item(0).getFirstChild()
String cName = classNode.getNodeValue()

// 通过类名生成实例对象并将其返回
Class c = Class.forName(cName)
Object obj = c.newInstance()
return obj

例子:document.createElement 创建 DOM 元素

例子:利用工厂模式,提供 axios.create 静态方法

import {AxiosStatic,AxiosInstance, AxiosRequestConfig} from './types'
import Axios from './core/Axios'
import {extend} from './helpers/util'
import defaults from './default'
import mergeConfig from './core/mergeConfig'


function createInstance(config:AxiosRequestConfig):AxiosStatic{
const context = new Axios(config)
// 把Axios类隐藏,直接返回其main方法
// 可以通过 实例({参数}) 直接使用
const instance = Axios.prototype.request.bind(context)
extend(instance, context)
// 这里不标注instance而是static,因为类升级了,增加静态属性
return instance as AxiosStatic
}


const axios = createInstance(defaults)


// 增加静态create方法
axios.create = function create(config: AxiosRequestConfig | undefined){
return createInstance(mergeConfig(defaults,config))
}
export default axios

工厂负责创建产品,产品封装对象的具体实现

/* 产品类1 */
class Product1 {
constructor() {
this.type = "Product1";
}
operate() {
console.log(this.type);
}
}

/* 产品类2 */
class Product2 {
constructor() {
this.type = "Product2";
}
operate() {
console.log(this.type);
}
}

/* 工厂类 */
class Factory {
static getInstance(type) {
switch (type) {
case "Product1":
return new Product1();
case "Product2":
return new Product2();
default:
throw new Error("当前没有这个产品");
}
}
}

const prod1 = Factory.getInstance("Product1");
prod1.operate(); // 输出: Product1
const prod2 = Factory.getInstance("Product3"); // 输出: Error 当前没有这个产品

为什么使用工厂方法呢? 因为 new SomeClass 来构建对象本质上属于硬编码写死了类型。

为了将 object 的构建和使用分开,才引入了工厂函数作为中间抽象。

上面的例子中,使用工厂方法之后,还可以将 CarFactory.create 的参数写在配置文件,或者通过命令行传递。这样如果需要改变车辆品牌,只需要修改配置或者参数即可,不用修改编译代码。

仔细想一下,工厂方法其实和下面的代码没有本质区别。

之前 MAX 是硬编码的某个值,比如 100; 之后引入了函数 getMax,至于 getMax 返回的值是怎么来的我们不关心,只要是 int 类型即可。 getMax 的角色和工厂方法是一样的。

简单工厂模式,只有单工厂,且工厂是一个类

// 简单工厂模式(静态工厂模式),用一个工厂对象创建同一类对象的实例
function Factory(career){
function User(carrer,work){
this.career = carrer
this.work = work
}
let work
swich(carrer){
case '清洁工'
work = ['扫地']
return new User(carrer,work)
break
case 'hr':
work = ['招聘']
return new User(carrer,work)
break
}
}


let qjg = new Factory('清洁工')

工厂方法模式,多工厂,每个工厂对应一种产品

// 工厂方法模式

Factory.prototype = {
coder: function () {
this.careerName = "程序员";
this.work = "写代码";
},
};
function Factory(career) {
// 如果this不是Factory,自然也找不到prototype上的“工厂方法”了
if (this instanceof Factory) {
let a = new this[career]();
return a;
}
}

console.log(Factory.call(undefined, "coder"));
// coder {careerName: '程序员', work: '写代码'}

对应 java

// 具体工厂类
public class FactoryA implements Factory {
public Product createProduct() {
return new ProductA();
}
}

public class FactoryB implements Factory {
public Product createProduct() {
return new ProductB();
}
}

抽象工厂,抽象工厂是一个接口,可以被多个具体工厂继承

// 抽象工厂接口
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}

// 具体工厂类
public class ConcreteFactory1 implements AbstractFactory {
public ProductA createProductA() {
return new ConcreteProductA1();
}

public ProductB createProductB() {
return new ConcreteProductB1();
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
AbstractFactory factory = new ConcreteFactory1();
ProductA productA = factory.createProductA();
ProductB productB = factory.createProductB();
productA.use();
productB.consume();
}
}

总结

  • 工厂模式是简单工厂的抽象和扩展
  • 工厂方法模式保留了简单工厂的优点同时克服了其缺陷
  • 中心工厂类不再负责所有产品的创建;而把具体的创建人物交给了子类
  • 工厂方法允许了引入新产品而不用修改工厂职责
  • 把添加扩展产品变为添加扩展工厂,符合开闭原则

优点

  • 开闭原则
  • 允许工厂独立决定将制造的产品
  • 隐藏了具体产品被创建的细节

缺陷

  • 系统中的类将成对地增长,可能提升系统的复杂度,带来额外的开销
  • 提升了系统的抽象度和理解困难度