工厂模式
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。所谓工厂模式就是将创建对象的过程单独封装。
概述(大白话)
- 将 new 操作单独封装
- 遇到 new 时,就要考虑是否使用工厂模式
根据抽象程度的不同可以分为
抽象:将复杂事物的一个或多个共有特征抽取出来的思维过程。
简单工厂模式(Simple Factory)
简单工厂实际不能算作一种设计模式,它引入了创建者的概念,将实例化的代码从应用代码中抽离,在创建者类的静态方法中只处理创建对象的细节,后续创建的实例如需改变,只需改造创建者类即可,
但由于使用静态方法来获取对象,使其不能在运行期间通过不同方式去动态改变创建行为,因此存在一定局限性。
UML 类图
代码示例
// src/demo3/a.ts
type fnType = () => void;
interface IProduct {
doSomething: fnType;
}
// 定义类
class ProductA implements IProduct {
doSomething() {
console.log('A');
// ...
}
}
class ProductB implements IProduct {
doSomething() {
console.log('B');
// ...
}
}
class ProductC implements IProduct {
doSomething() {
console.log('C');
// ...
}
}
/* 简单工厂 */
class SimpleFactory {
createProduct(type: string): IProduct | null {
let instance: IProduct | null = null;
switch (type) {
case 'A':
instance = new ProductA();
break;
case 'B':
instance = new ProductB();
break;
case 'C':
instance = new ProductC();
break;
}
return instance;
}
}
// 使用
const client = new SimpleFactory();
(client.createProduct('A') as IProduct).doSomething();
(client.createProduct('B') as IProduct).doSomething();
export {};
IProduct接口:定义要创建的产品对象的接口ProductA、ProductB、ProductC产品类:实现产品接口,具有产品接口特性的具体产品SimpleFactory简单工厂:只有一个工厂,通过静态方法createProduct创建具体的产品对象client客户端:客户端有多个,每个客户端可以通过简单工厂来创建具体的产品对象
优缺点
- 优点:避免直接创建对象
- 缺点:违背开闭原则,如果需要新增 Product 类,需要在工厂类新增逻辑判断
- 适用于业务简单,产品固定不会经常改变的情况。
工厂方法模式(Factory Method)
工厂方法模式实际上是简单工厂模式的升级,工厂方法模式定义除了产品接口外,还定义了一个用于创建对象工厂的接口,让工厂子类再去实例化对应的产品类。
UML 类图
IProduct接口:和简单工厂相同,提供产品对象的接口ProductA、ProductB和productC:具体类型的产品对象FactoryA、FactoryB和FactoryC:具体的产品工厂,实现具体的产品对象AbstractFactory:抽象工厂,可以有多个,其中的方法负责返回创建的产品对象Client:使用该模式的客户端
代码示例
// src/demo03/b.ts
type fnType = () => void;
interface IProduct {
doSomething: fnType;
}
/* 具体产品实现 */
class ProductA implements IProduct {
doSomething() {
console.log('ProductA');
// ...
}
}
class ProductB implements IProduct {
doSomething() {
console.log('ProductB');
// ...
}
}
/* 工厂接口 */
interface AbstractFactory {
createProduct: () => IProduct;
}
/* A 工厂 */
class FactoryA implements AbstractFactory {
createProduct() {
return new ProductA();
}
}
/* B 工厂 */
class FactoryB implements AbstractFactory {
createProduct() {
return new ProductB();
}
}
// 使用
const clientA = new FactoryA().createProduct();
clientA.doSomething();
const clientB = new FactoryB().createProduct();
clientB.doSomething();
export {};
其中最主要的是AbstractFactory类中的createProduct方法,通过这个方法来生成具体产品,这也是为什么叫工厂方法的原因。和简单工厂的静态方法不同,这里是使用的非静态调用方式。而且可以发现,没有了简单工厂中的 switch逻辑判断,相对而言扩展性也要强的多。
优缺点
- 优点:完全实现开闭原则,实现了可扩展和更复杂的层次结构。明确了职责,具有多态性,适用于任何实体类。
- 缺点:如果业务增加,会使得系统中类的个数成倍增加,提高了代码的复杂度
抽象工厂模式(Abstract Factory)
是围绕一个超级工厂创建其他工厂。
UML 类图
Product1和Product2:定义一种类型的产品对象接口Product1A、Product1B等:各种类型的具体产品对象FactoryA和FactoryB:具体产品工厂,负责创建该工厂类型下的产品对象AbstractFactory:抽象工厂接口,定义一类产品对象Client:客户端,使用抽象工厂,调用产品对象
代码示例
// src/demo3/c.ts
type fnType = () => void;
/* Product1 类的产品接口 */
interface IProduct1 {
doSomething: fnType;
}
class Product1A implements IProduct1 {
doSomething() {
console.log('Product1A');
// ...
}
}
class Product1B implements IProduct1 {
doSomething() {
console.log('Product1B');
// ...
}
}
/* Product2 类的产品接口 */
interface IProduct2 {
doSomething: fnType;
}
class Product2A implements IProduct2 {
doSomething() {
console.log('Product2A');
// ...
}
}
class Product2B implements IProduct2 {
doSomething() {
console.log('Product2B');
// ...
}
}
/* 工厂接口 */
interface AbstractFactory {
createProduct1(): IProduct1;
createProduct2(): IProduct2;
}
/* A 工厂 */
class FactoryA implements AbstractFactory {
createProduct1() {
return new Product1A();
}
createProduct2() {
return new Product2A();
}
}
/* B 工厂 */
class FactoryB implements AbstractFactory {
createProduct1() {
return new Product1B();
}
createProduct2() {
return new Product2B();
}
}
// 使用
const clientA = new FactoryA().createProduct1();
clientA.doSomething();
const clientB = new FactoryB().createProduct2();
clientB.doSomething();
export {};
优缺点
- 优点:增加分组比较容易,而且能大大减少工厂类的数量
- 缺点:因为分组,所以分组中的产品扩展就比较困难,比如再新增一个 Product3,就需要改动
AbstractFactory、FactoryA和FactoryB几乎所有工厂类
应用场景
JQuery
$('div') 和 new JQuery('div')有何区别?左边是工厂模式,如果选择后者
- 1、书写麻烦,JQuery 的链式操作将成为噩梦
- 2、一旦 JQuery 名字发生变化,将是灾难性的
declare interface Window {
$: (seletor: string) => JQuery;
}
class JQuery {
seletor: string;
length: number;
constructor(seletor: string) {
const domList = Array.prototype.slice.call(document.querySelectorAll(seletor));
const length = domList.length;
for (let i = 0; i < length; i++) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this[i] = domList[i];
}
this.seletor = seletor;
this.length = length;
}
append(elem: HTMLElement): JQuery {
console.log('append: ', elem);
// ...相关操作
return this;
}
addClass(className: string): JQuery {
console.log('addClass: ', className);
// ...相关操作
return this;
}
// ...
}
// 不用工厂模式
const $div = new JQuery('div');
const $p = new JQuery('p');
// 用工厂模式
function $(seletor: string) {
return new JQuery(seletor);
}
const $div1 = $('div');
const $p1 = $('p');
window.$ = $;
// 伪代码,演示jquery
// const jquery = {
// seletor: 'div',
// length: 3,
// 0: div1,
// 1: div2,
// 2: div3
// }
export {};
React.createElement 和 Vue _createElementVNode
createElement
// https://www.babeljs.cn/repl
// createElement前
<div>
<h1 id={hello}>hi H1</h1>
<p>{item?.p} abc</p>
</div>;
// createElement后
('use strict');
var _item;
/*#__PURE__*/ React.createElement(
'div',
null,
/*#__PURE__*/ React.createElement(
'h1',
{
id: hello,
},
'hi H1',
),
/*#__PURE__*/ React.createElement('p', null, (_item = item) === null || _item === void 0 ? void 0 : _item.p, ' abc'),
);
_createElementVNode
// https://vue-next-template-explorer.netlify.app/
// _createElementVNode前
<div>
<h1 :id="hello">hi H1</h1>
<p>{{item?.p}} abc</p>
</div>
// _createElementVNode后
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("h1", { id: _ctx.hello }, "hi H1", 8 /* PROPS */, ["id"]),
_createElementVNode("p", null, _toDisplayString(_ctx.item?.p) + " abc", 1 /* TEXT */)
]))
}
// Check the console for the AST
设计原则验证
- 构造函数和创建者分离
- 符合开放封闭原则