工厂模式

2026 年 2 月 18 日 星期三(已编辑)
4

工厂模式

在日常开发中,我们经常需要根据不同条件创建不同的对象。最直接的方式是在代码中到处使用 new,但这样会带来严重的耦合问题。工厂模式正是为了解决这个问题而生的:它让代码不再直接依赖具体类,而是通过“工厂”来创建对象,从而实现解耦。

工厂模式的核心思想是:不要直接 new,让工厂来创建。通过引入工厂这一中间层,实现客户端代码与具体产品类的解耦,让代码更加灵活、易维护、易扩展。

在前端开发中,它尤其适合用于:

  • HTTP 客户端的创建与配置
  • 表单验证器的统一管理
  • UI 组件的动态生成
  • 各类策略对象、解析器、适配器的生产与注册

当你发现代码中到处都是 new,且创建逻辑很相似或频繁改动时,就是考虑工厂模式的好时机。

它通常带来这些收益:

  • 解耦对象创建:客户端不直接依赖具体类
  • 封装创建逻辑:复杂初始化过程隐藏在工厂内部
  • 统一创建入口:提供一致的对象获取方式
  • 易于扩展:新增产品类型时尽量不改客户端代码

1. 为什么需要工厂模式?

1.1 直接使用 new 的痛点

假设你在做数据可视化应用,需要根据用户选择创建不同类型的图表:

// ❌ 糟糕的做法:到处都是 new,强耦合
function createChart(type, data) {
  let chart;

  if (type === "line") {
    chart = new LineChart(data);
    chart.setAxisConfig({ x: "time", y: "value" });
    chart.enableTooltip();
  } else if (type === "bar") {
    chart = new BarChart(data);
    chart.setColors(["#ff6384", "#36a2eb"]);
    chart.enableAnimation();
  } else if (type === "pie") {
    chart = new PieChart(data);
    chart.enableLegend();
    chart.setLabelFormat("percentage");
  }

  return chart;
}

const lineChart = createChart("line", salesData);
const barChart = createChart("bar", revenueData);

这种写法常见问题:

  • 强耦合createChart 直接依赖所有具体图表类
  • 难以扩展:每新增一种图表类型,都要修改 createChart
  • 违反开闭原则:应该“对扩展开放,对修改封闭”,但这里每次扩展都在改代码

---

2. 简单工厂模式(Simple Factory)

适用于:产品类型相对固定、创建逻辑集中管理的场景。

2.1 产品抽象与具体产品

// 产品基类(抽象概念)
class Chart {
  constructor(data) {
    this.data = data;
  }

  render() {
    throw new Error("子类必须实现 render 方法");
  }
}

class LineChart extends Chart {
  constructor(data) {
    super(data);
    this.type = "line";
  }
  setAxisConfig() {}
  enableTooltip() {}
  render() {
    console.log("渲染折线图");
  }
}

class BarChart extends Chart {
  constructor(data) {
    super(data);
    this.type = "bar";
  }
  setColors() {}
  enableAnimation() {}
  render() {
    console.log("渲染柱状图");
  }
}

class PieChart extends Chart {
  constructor(data) {
    super(data);
    this.type = "pie";
  }
  enableLegend() {}
  setLabelFormat() {}
  render() {
    console.log("渲染饼图");
  }
}

2.2 工厂:封装创建逻辑

// ✅ 工厂:把“选择哪种类 + 如何初始化”的逻辑集中起来
class ChartFactory {
  static createChart(type, data, options = {}) {
    let chart;

    switch (type) {
      case "line":
        chart = new LineChart(data);
        chart.setAxisConfig(options.axis || { x: "time", y: "value" });
        chart.enableTooltip();
        break;

      case "bar":
        chart = new BarChart(data);
        chart.setColors(options.colors || ["#ff6384", "#36a2eb"]);
        chart.enableAnimation();
        break;

      case "pie":
        chart = new PieChart(data);
        chart.enableLegend();
        chart.setLabelFormat(options.labelFormat || "percentage");
        break;

      default:
        throw new Error(`不支持的图表类型: ${type}`);
    }

    return chart;
  }
}

客户端使用:

const lineChart = ChartFactory.createChart("line", salesData);
const barChart = ChartFactory.createChart("bar", revenueData, {
  colors: ["#ff9999", "#66b3ff"],
});
const pieChart = ChartFactory.createChart("pie", categoryData);

[lineChart, barChart, pieChart].forEach((chart) => chart.render());

简单工厂的优点是:调用方更干净。缺点是:如果产品类型不断增加,工厂内部的 switch/case 会膨胀,仍需要修改工厂代码。


4. 工厂方法模式(Factory Method)

适用于:希望更强扩展性、让“创建逻辑随着类型扩展而扩展”,并尽量避免改动原有工厂代码的场景。

核心思路:把“创建哪种产品”的决定,交给不同的具体工厂类。

4.1 抽象工厂与具体工厂

class ChartFactory {
  createChart(data, options) {
    throw new Error("子类必须实现 createChart 方法");
  }
}

class LineChartFactory extends ChartFactory {
  createChart(data, options = {}) {
    const chart = new LineChart(data);
    chart.setAxisConfig(options.axis || { x: "time", y: "value" });
    chart.enableTooltip();
    return chart;
  }
}

class BarChartFactory extends ChartFactory {
  createChart(data, options = {}) {
    const chart = new BarChart(data);
    chart.setColors(options.colors || ["#ff6384", "#36a2eb"]);
    chart.enableAnimation();
    return chart;
  }
}

4.2 工厂注册器:实现动态扩展

class ChartFactoryRegistry {
  static factories = new Map();

  static register(type, factory) {
    this.factories.set(type, factory);
  }

  static createChart(type, data, options) {
    const factory = this.factories.get(type);
    if (!factory) throw new Error(`未注册的图表类型: ${type}`);
    return factory.createChart(data, options);
  }

  static getSupportedTypes() {
    return Array.from(this.factories.keys());
  }
}

ChartFactoryRegistry.register("line", new LineChartFactory());
ChartFactoryRegistry.register("bar", new BarChartFactory());

新增类型时,只需要新增产品类 + 新增工厂并注册,不必改动老代码:

class ScatterChart extends Chart {
  constructor(data) {
    super(data);
    this.type = "scatter";
  }
  setPointSize() {}
  render() {
    console.log("渲染散点图");
  }
}

class ScatterChartFactory extends ChartFactory {
  createChart(data, options = {}) {
    const chart = new ScatterChart(data);
    chart.setPointSize(options.pointSize || 3);
    return chart;
  }
}

ChartFactoryRegistry.register("scatter", new ScatterChartFactory());

使用:

const charts = [
  ChartFactoryRegistry.createChart("line", salesData),
  ChartFactoryRegistry.createChart("bar", revenueData),
  ChartFactoryRegistry.createChart("scatter", correlationData),
];

console.log("支持的图表类型:", ChartFactoryRegistry.getSupportedTypes());

4. 前端实际应用场景

4.1 HTTP 客户端工厂

不同 API 需要不同配置的 HTTP Client(baseURL、超时、header 等):

class HttpClient {
  constructor(config) {
    this.baseURL = config.baseURL;
    this.timeout = config.timeout;
    this.headers = config.headers;
  }

  async get(url) {
    console.log(`GET ${this.baseURL}${url}`);
    return { data: "mock data" };
  }
}

class HttpClientFactory {
  static createClient(type) {
    const configs = {
      api: {
        baseURL: "https://api.example.com",
        timeout: 5000,
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      },
      upload: {
        baseURL: "https://upload.example.com",
        timeout: 30000,
        headers: { "Content-Type": "multipart/form-data" },
      },
      analytics: {
        baseURL: "https://analytics.example.com",
        timeout: 2000,
        headers: { "X-API-Key": process.env.ANALYTICS_API_KEY },
      },
    };

    const config = configs[type];
    if (!config) throw new Error(`不支持的客户端类型: ${type}`);
    return new HttpClient(config);
  }
}

const apiClient = HttpClientFactory.createClient("api");
const uploadClient = HttpClientFactory.createClient("upload");

apiClient.get("/users");
uploadClient.get("/files");

4.2 表单验证器工厂

统一校验接口,按类型生产不同校验器:

class EmailValidator {
  validate(value) {
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    return { valid, message: valid ? "" : "请输入有效的邮箱地址" };
  }
}

class PhoneValidator {
  validate(value) {
    const valid = /^1[3-9]\d{9}$/.test(value);
    return { valid, message: valid ? "" : "请输入有效的手机号码" };
  }
}

class ValidatorFactory {
  static createValidator(type) {
    const validators = {
      email: () => new EmailValidator(),
      phone: () => new PhoneValidator(),
    };

    const factory = validators[type];
    if (!factory) throw new Error(`不支持的验证器类型: ${type}`);
    return factory();
  }
}

const emailValidator = ValidatorFactory.createValidator("email");
const phoneValidator = ValidatorFactory.createValidator("phone");

console.log(emailValidator.validate("test@example.com"));
console.log(phoneValidator.validate("13800138000"));

4.3 React 组件工厂(动态生成组件)

const Button = ({ variant, children, ...props }) => {
  const classes = {
    primary: "bg-blue-500 text-white",
    secondary: "bg-gray-500 text-white",
  };

  return (
    <button className={`px-4 py-2 rounded ${classes[variant]}`} {...props}>
      {children}
    </button>
  );
};

const Input = ({ error, ...props }) => (
  <div>
    <input
      className={`border rounded px-3 py-2 ${
        error ? "border-red-500" : "border-gray-300"
      }`}
      {...props}
    />
    {error && <p className="text-red-500 text-sm mt-1">{error}</p>}
  </div>
);

class ComponentFactory {
  static components = {
    button: Button,
    input: Input,
  };

  static createComponent(type, props) {
    const Component = this.components[type];
    if (!Component) throw new Error(`不支持的组件类型: ${type}`);
    return React.createElement(Component, props);
  }
}

const MyForm = () => {
  return (
    <div>
      {ComponentFactory.createComponent("input", { placeholder: "请输入用户名" })}
      {ComponentFactory.createComponent("button", {
        variant: "primary",
        children: "提交",
      })}
    </div>
  );
};

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...