工厂模式:一种创建对象的优雅方案

77 阅读11分钟

一、工厂模式基础认知

1.1 工厂模式是什么?

工厂模式是一种封装对象创建逻辑的设计模式,它的核心思想是:将 “对象的创建” 与 “对象的使用” 分离。

比如,我们以画图为例,假如我们平时一般通过new来创建图形对象,那么,在有了工厂模式之后,我们就可以通过一个 “工厂” 类或函数来统一负责图形创建,而使用时只需告知工厂我们 “需要什么类型的图形”,从而无需关心创建细节。

1.2 为什么需要工厂模式?

为了容易理解,我们先来看一段不使用工厂模式创建图形的代码。

无工厂模式的实现代码

// 1. 定义图形类
class Circle {
  constructor(color) {
    this.type = "圆形";
    this.color = color;
    console.log("创建一个"+ this.color + this.type);
  }

  // 绘制方法
  draw() {
    console.log("画一个" + this.color + this.type);
  }
}

class Rectangle {
  constructor(color) {
    this.type = "矩形";
    this.color = color;
    console.log("创建" + this.color + this.type);
  }

  draw() {
    console.log("画一个" + this.color + this.type);
  }
}

// 2. 使用图形:直接在绘图时 new一个对象
function drawShapes() {
  // 问题1:创建逻辑与绘图逻辑耦合,必须知道所有图形类名
  const redCircle = new Circle("红色");
  const blueRectangle = new Rectangle("蓝色");

  // 问题2:若要更换图形类型(如换成绿色圆形、黄色矩形),需修改所有new的地方,比如:
  // const greenCircle = new Circle("绿色");
  // const yellowRectangle = new Rectangle("黄色");

  // 问题3:若新增图形(如三角形),需在所有绘图处添加new Triangle()
  redCircle.draw();
  blueRectangle.draw();
}

drawShapes();

总结:无工厂模式的 4 大痛点

  1. 耦合度高:绘图时必须知道具体图形的类名(如Circle),若类名修改(如改Round),所有绘图处都要改。
  2. 重复代码多:若创建图形需要复杂初始化(比如添加圆心坐标、矩形宽高等),每个绘图处都要重复写一遍参数。
  3. 扩展困难:如果新增图形(如三角形、梯形)时,需要在所有绘图逻辑中添加新的new代码。
  4. 维护成本高:图形创建逻辑分散在各处,后期要统一修改规则(如给所有图形加一个坐标属性),还需逐个修改绘图处。

二、入门:简单工厂模式(Simple Factory)

简单工厂是工厂模式中的 “基础版本”—— 通过一个统一的工厂类或函数,然后根据传入的参数来判断 “要创建什么图形”,并返回对应的图形实例。

2.1 简单工厂模式的实现

我们先用简单工厂重构代码,将所有图形创建逻辑集中到ShapeFactory

// 1. 定义图形类(这里与无工厂模式一致,无需修改)
class Circle {
  constructor(color) {
    this.type = "圆形";
    this.color = color;
    console.log("创建一个"+ this.color + this.type);
  }

  // 绘制方法
  draw() {
    console.log("画一个" + this.color + this.type);
  }
}

class Rectangle {
  constructor(color) {
    this.type = "矩形";
    this.color = color;
    console.log("创建" + this.color + this.type);
  }

  draw() {
    console.log("画一个" + this.color + this.type);
  }
}

// 2. 简单工厂:实现集中管理图形创建逻辑
class ShapeFactory {
  // 通过一个静态方法:根据参数创建对应图形
  static createShape(type, color) {
    // 统一处理创建逻辑(如参数校验)
    if (!type || !color) throw new Error("请传入图形类型和颜色");

    // 根据参数返回不同图形实例
    switch (type) {
      case "圆形":
        return new Circle(color);
      case "矩形":
        return new Rectangle(color);
      default:
        throw new Error("不支持该图形类型");
    }
  }
}

// 3. 使用图形:只需调用工厂,而无需关心创建细节
function drawShapes() {
  // 优势1:不用记具体类名,只需传参数(图形类型+颜色)
  const redCircle = ShapeFactory.createShape("圆形", "红色");
  const blueRectangle = ShapeFactory.createShape("矩形", "蓝色");

  // 优势2:更换图形或颜色只需改参数,不用改new逻辑,比如:
  // const greenCircle = ShapeFactory.createShape("圆形", "绿色");
  // const yellowRectangle = ShapeFactory.createShape("矩形", "黄色");

  redCircle.draw();
  blueRectangle.draw();
}

drawShapes();

2.2 总结:简单工厂的核心特性及优缺点

  • 单一入口:所有图形通过工厂的createShape方法统一创建,绘图时只要调用工厂的createShape,不用接触具体的图形类。

  • 参数驱动:通过type(图形类型)和color(颜色)参数来控制创建的图形,可以更灵活地切换图形类型。

  • 集中维护:若要给所有图形加初始化逻辑(如this.borderWidth = 2),只需在工厂中统一处理,不用修改每个图形类。

    优点缺点
    降低耦合:绘图逻辑与图形类解耦违反开闭原则:新增图形需修改工厂的 switch 逻辑
    减少重复:创建逻辑集中,避免冗余参数代码工厂职责过重:所有图形创建逻辑都在一个工厂
    易于维护:修改创建规则只需改工厂不适合图形类型极多的场景(如 10 + 种图形)

三、进阶:工厂方法模式(Factory Method)

如果要说简单工厂还有什么问题,那就是违反开闭原则,即:新增图形时必须修改工厂代码

工厂方法模式,我们通过 “抽象工厂 + 具体工厂” 的拆分,可以让新增图形时无需修改旧代码,完美符合开闭原则。

3.1 工厂方法模式的核心思想

将简单工厂的 “单一工厂” 进一步拆分为两层:

  1. 抽象工厂(Abstract Factory) :用来定义一个 “创建图形” 的接口(如createShape),不关心具体实现。

  2. 具体工厂(Concrete Factory) :每种图形对应一个具体工厂,具体工厂实现抽象工厂的接口,负责创建对应类型的图形。

简单来说,工厂方法就像 “图形专属工厂”:圆形有圆形工厂(负责造各种颜色的圆形),矩形有矩形工坊(负责造各种颜色的矩形),新增三角形时,只需新建三角形工厂,不用向简单工厂模式一样修改圆形或矩形工厂的代码。

3.2 工厂方法模式的实现

我们用工厂方法重构代码,实现 “新增图形无需改旧代码”。

// 1. 定义抽象图形(统一图形方法,确保所有图形有相同接口)
class AbstractShape {
    constructor(color) {
        this.color = color;
    }

    // 抽象方法:子类必须实现draw
    draw() {
        throw new Error("图形子类必须实现draw方法");
    }
}

// 2. 定义具体图形(具体图形继承自上面的抽象图形,实现draw方法)
// 圆形
class Circle extends AbstractShape {
    constructor(color) {
        super(color);
        this.type = "圆形";
    }

    draw() {
        console.log("画一个" + this.color + this.type);
    }
}
//矩形
class Rectangle extends AbstractShape {
    constructor(color) {
        super(color);
        this.type = "矩形";
    }

    draw() {
        console.log("画一个" + this.color + this.type);
    }
}

// 3. 定义抽象工厂(定义创建图形的接口)
class AbstractShapeFactory {
    // 抽象方法:子类必须实现createShape
    createShape(color) {
        throw new Error("工厂子类必须实现createShape方法");
    }
}

// 4. 定义具体工厂(具体工厂继承抽象工厂,每种图形对应一个工厂)
class CircleFactory extends AbstractShapeFactory {
    // 圆形工厂:只创建圆形
    createShape(color) {
        return new Circle(color);
    }
}

class RectangleFactory extends AbstractShapeFactory {
    // 矩形工厂:只创建矩形
    createShape(color) {
        return new Rectangle(color);
    }
}

// 5. 使用图形:通过具体工厂获取图形
function drawShapes(shapeFactory, color) {
    // 优势:绘图逻辑与图形类型完全解耦,传入不同工厂就是不同图形
    const shape = shapeFactory.createShape(color);
    shape.draw();
}

// 绘制红色圆形:传入圆形工厂+红色
drawShapes(new CircleFactory(), "红色");
// 绘制蓝色矩形:传入矩形工厂+蓝色(无需改drawShapes逻辑)
drawShapes(new RectangleFactory(), "蓝色");

// 新增三角形:只需新建三角形图形和三角形工厂,不用改旧的代码
class Triangle extends AbstractShape {
    constructor(color) {
        super(color);
        this.type = "三角形";
    }
    draw() {
        console.log(`在画布上绘制${this.color}${this.type}`);
    }
}

class TriangleFactory extends AbstractShapeFactory {
    createShape(color) {
        return new Triangle(color);
    }
}

// 绘制绿色三角形:直接传入三角形工厂
drawShapes(new TriangleFactory(), "绿色");

3.3 总结:工厂方法的核心特性与优缺点

  • 开闭原则友好:新增图形(如三角形)时,只需新建具体图形和具体工厂,无需修改抽象工厂或旧的具体工厂。

  • 职责单一:每个具体工厂只负责创建对应类型的图形(如圆形工厂只造圆形),避免 “一个工厂管所有” 的问题。

  • 接口统一:抽象图形和抽象工厂定义了统一接口,确保所有图形的draw方法、所有工厂的createShape方法一致,降低代码复杂度。

    优点缺点
    符合开闭原则:新增图形无需改旧代码类数量增多:每种图形对应 2 个类(图形 + 工厂)
    职责单一:工厂只负责对应图形的创建实现复杂:相比简单工厂,需要定义更多抽象和具体类
    扩展性强:支持动态切换工厂和图形不适合简单场景:小绘图项目用工厂方法会增加冗余

四、高级:抽象工厂模式(Abstract Factory)

工厂方法模式适合 “单一类型图形” 的场景,比如只创建圆形或只创建矩形,但如果需要更多的图形风格组合,比如实线风格的圆形 + 矩形、虚线风格的圆形 + 矩形,此时,则需要抽象工厂模式:

抽象工厂模式能创建 “一组相关联的图形”,确保图形风格的一致性。

4.1 抽象工厂模式的核心思想

抽象工厂模式的核心是 “创建图形风格族”:

  • 图形风格族:同一风格下的一组相关图形(如实线风格族:实线圆形、实线矩形;虚线风格族:虚线圆形、虚线矩形);

  • 抽象工厂:定义创建 “风格族中所有图形” 的接口(如createCirclecreateRectangle);

  • 具体工厂:每种风格对应一个具体工厂,实现抽象工厂的所有接口,创建该风格的所有图形。

简单来说,抽象工厂就像 “风格专属工厂”,实线工厂不仅造实线圆形,还造实线矩形,所有图形都是实线风格;虚线工厂造的都是虚线风格,确保 “一幅图中所有图形风格统一”。

4.2 抽象工厂模式的实现

我们现在添加新的属性,支持 实线和虚线 两种风格的图形族创建:

// 1. 定义图形风格族的抽象图形(圆形、矩形)
class AbstractCircle {
    constructor(color, style) {
        this.type = "圆形";
        this.color = color;
        this.style = style; // 新增“风格”属性(实线/虚线)
    }
    draw() { }
}

class AbstractRectangle {
    constructor(color, style) {
        this.type = "矩形";
        this.color = color;
        this.style = style;
    }
    draw() { }
}

// 2. 定义实线风格图形族(所有图形都是实线)
class SolidCircle extends AbstractCircle {
    constructor(color) {
        super(color, "实线");
    }
    draw() {
        console.log("画一个" + this.color + this.style + this.type);
    }
}

class SolidRectangle extends AbstractRectangle {
    constructor(color) {
        super(color, "实线");
    }
    draw() {
        console.log("画一个" + this.color + this.style + this.type);
    }
}

// 3. 定义虚线风格图形族(所有图形都是虚线)
class DashedCircle extends AbstractCircle {
    constructor(color) {
        super(color, "虚线");
    }
    draw() {
        console.log("画一个" + this.color + this.style + this.type);
    }
}

class DashedRectangle extends AbstractRectangle {
    constructor(color) {
        super(color, "虚线");
    }
    draw() {
        console.log("画一个" + this.color + this.style + this.type);
    }
}

// 4. 定义抽象工厂(创建风格族的所有图形)
class AbstractStyleFactory {
    createCircle(color) { throw new Error("需实现createCircle"); }
    createRectangle(color) { throw new Error("需实现createRectangle"); }
}

// 5. 定义具体工厂(每种风格对应一个工厂)
class SolidStyleFactory extends AbstractStyleFactory {
    // 实线工厂:只造实线图形
    createCircle(color) {
        return new SolidCircle(color);
    }
    createRectangle(color) {
        return new SolidRectangle(color);
    }
}

class DashedStyleFactory extends AbstractStyleFactory {
    // 虚线工厂:只造虚线图形
    createCircle(color) {
        return new DashedCircle(color);
    }
    createRectangle(color) {
        return new DashedRectangle(color);
    }
}

// 6. 使用图形风格族:获取同一风格的所有图形
function drawStyleFamily(styleFactory) {
    // 同一风格工厂创建的图形,风格自动统一
    const redCircle = styleFactory.createCircle("红色");
    const blueRectangle = styleFactory.createRectangle("蓝色");

    redCircle.draw();
    blueRectangle.draw();
}

// 绘制实线风格图形族
console.log("实线风格");
drawStyleFamily(new SolidStyleFactory());
// 绘制虚线风格图形族
console.log("虚线风格");
drawStyleFamily(new DashedStyleFactory());

// 新增点线风格族:只需新建点线图形和点线工厂
class DottedCircle extends AbstractCircle { /* 实现点线圆形draw */ }
class DottedRectangle extends AbstractRectangle { /* 实现点线矩形draw */ }
class DottedStyleFactory extends AbstractStyleFactory {
    createCircle(color) { return new DottedCircle(color); }
    createRectangle(color) { return new DottedRectangle(color); }
}
console.log("点线风格");
drawStyleFamily(new DottedStyleFactory());

4.3 抽象工厂的核心特性及优缺点

优点:

  • 风格一致性:同一具体工厂创建的图形都属于同一风格族(如实线工厂只造实线图形),避免 “实线圆形 + 虚线矩形” 的混搭,适合需要统一风格的绘图场景(如报表图表、UI 设计图)。
  • 封闭风格族扩展:新增风格(如点线)只需新建具体工厂和对应图形,无需修改旧代码。

缺点:

  • 限制图形类型扩展:若要新增图形类型(如在圆形、矩形外加 “三角形”),还是需要修改抽象工厂接口,违反了之前提到的开闭原则。

五、无工厂模式 & 工厂模式之间 的对比总结

对比属性无工厂模式工厂模式
耦合度高:绘图逻辑依赖具体图形类名低:绘图逻辑依赖工厂,不依赖图形类
代码重复多:创建逻辑分散,重复写参数和初始化少:创建逻辑集中,统一维护参数和风格
扩展性差:新增图形需修改所有绘图处好:新增图形 / 风格只需扩展工厂,不修改旧代码
维护成本高:修改创建规则(如加风格)需逐个改绘图处低:修改规则只需改工厂,一处生效
一致性差:易出现风格混搭(如实线圆 + 虚线矩形)好:抽象工厂确保风格族一致性

工厂模式类型核心特点适用场景缺点
简单工厂单一工厂,参数驱动创建图形图形少、变化少(如简单 Canvas 绘图)违反开闭原则,工厂职责重
工厂方法抽象工厂 + 具体工厂,单图形创建图形多、频繁新增(如图表库)类数量多,实现复杂
抽象工厂抽象工厂 + 具体工厂,风格族创建需统一风格(如 UI 组件库图形)扩展图形类型难,违反开闭原则