设计模式-工厂模式

🏭 深度解析工厂设计模式:解耦对象创建,让代码更优雅

前言

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

🤔 为什么需要工厂模式?

问题:直接使用 new 的痛苦

想象你正在开发一个数据可视化应用,需要根据用户选择创建不同类型的图表:

javascript 复制代码
// ❌ 糟糕的做法:到处都是 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);

这种做法的问题:

  1. 强耦合createChart 函数直接依赖所有具体的图表类
  2. 难以扩展 :每增加一种图表类型,都要修改 createChart 函数
  3. 违反开闭原则:对修改开放,对扩展封闭
  4. 代码重复:类似的创建逻辑可能散布在多个地方
  5. 测试困难:难以模拟和替换具体的图表实现

解决方案:工厂模式的核心思想

工厂模式的核心是 "不要直接 new,让工厂来创建"。它通过以下方式解决上述问题:

  1. 解耦对象创建:客户端不直接依赖具体类
  2. 封装创建逻辑:复杂的对象创建过程被隐藏在工厂内部
  3. 统一创建接口:提供一致的对象获取方式
  4. 易于扩展:新增产品类型无需修改客户端代码

🏗️ 工厂模式的实现

简单工厂模式

最基础的工厂实现,适合产品类型相对固定的场景:

javascript 复制代码
// 产品基类
class Chart {
  constructor(data) {
    this.data = data;
  }
  
  render() {
    throw new Error('子类必须实现 render 方法');
  }
}

// 具体产品
class LineChart extends Chart {
  constructor(data) {
    super(data);
    this.type = 'line';
  }
  
  render() {
    console.log('渲染折线图');
    // 具体的渲染逻辑
  }
}

class BarChart extends Chart {
  constructor(data) {
    super(data);
    this.type = 'bar';
  }
  
  render() {
    console.log('渲染柱状图');
    // 具体的渲染逻辑
  }
}

class PieChart extends Chart {
  constructor(data) {
    super(data);
    this.type = 'pie';
  }
  
  render() {
    console.log('渲染饼图');
    // 具体的渲染逻辑
  }
}

// ✅ 工厂类:封装对象创建逻辑
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();
});

工厂方法模式

当需要更好的扩展性时,使用工厂方法模式:

javascript 复制代码
// 抽象工厂基类
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;
  }
}

// 工厂注册器 - 实现动态扩展
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';
  }
  
  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());

🌐 前端实际应用场景

1. HTTP 客户端工厂

不同的 API 需要不同配置的 HTTP 客户端:

javascript 复制代码
class HttpClient {
  constructor(config) {
    this.baseURL = config.baseURL;
    this.timeout = config.timeout;
    this.headers = config.headers;
  }
  
  async get(url) {
    // 模拟 HTTP GET 请求
    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');

2. 表单验证器工厂

javascript 复制代码
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'));

3. React 组件工厂

jsx 复制代码
// 基础组件
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>
  );
};

🎯 工厂模式的核心价值

✅ 优势

  1. 解耦合:客户端不依赖具体产品类,只依赖抽象接口
  2. 易扩展:新增产品类型无需修改客户端代码
  3. 封装复杂性:隐藏对象创建的复杂逻辑
  4. 统一管理:集中处理对象创建,便于维护
  5. 符合开闭原则:对扩展开放,对修改封闭

⚠️ 注意事项

  1. 避免过度设计 :简单场景直接 new 可能更合适
  2. 性能考虑:工厂模式会增加一层抽象,但通常影响微乎其微
  3. 类型安全:在 TypeScript 中要注意工厂返回类型的定义

📋 何时使用工厂模式

  • 需要根据条件创建不同类型的对象
  • 对象创建过程复杂,需要封装
  • 希望客户端代码不依赖具体的产品类
  • 需要统一管理一类对象的创建
  • 经常需要扩展新的产品类型

📝 总结

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

在前端开发中,工厂模式特别适合用于:

  • HTTP 客户端的创建和配置
  • 表单验证器的统一管理
  • UI 组件的动态生成
  • 各种策略对象的创建

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

相关推荐
尤超宇15 分钟前
基于随机森林的红酒分类与特征重要性分析
算法·随机森林·分类
Dragon Wu17 分钟前
前端 下载后端返回的二进制excel数据
前端·javascript·html5
北海几经夏23 分钟前
React响应式链路
前端·react.js
花火|43 分钟前
算法训练营day58 图论⑧ 拓扑排序精讲、dijkstra(朴素版)精讲
算法·图论
晴空雨1 小时前
React Media 深度解析:从使用到 window.matchMedia API 详解
前端·react.js
一个有故事的男同学1 小时前
React性能优化全景图:从问题发现到解决方案
前端
探码科技1 小时前
2025年20+超实用技术文档工具清单推荐
前端
Juchecar1 小时前
Vue 3 推荐选择组合式 API 风格(附录与选项式的代码对比)
前端·vue.js
uncleTom6661 小时前
# 从零实现一个Vue 3通用建议选择器组件:设计思路与最佳实践
前端·vue.js
影i1 小时前
iOS WebView 异步跳转解决方案
前端