01-创建型模式

第一部分:创建型模式

创建型模式关注对象的创建机制,让你的代码更灵活、更易维护

目录

  • [单例模式 (Singleton)](#单例模式 (Singleton) "#%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F")
  • [工厂模式 (Factory)](#工厂模式 (Factory) "#%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F")
  • [依赖注入模式 (Dependency Injection)](#依赖注入模式 (Dependency Injection) "#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5%E6%A8%A1%E5%BC%8F")
  • [建造者模式 (Builder)](#建造者模式 (Builder) "#%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F")

单例模式

💡 模式定义

确保一个类只有一个实例,并提供全局访问点。

🤔 为什么需要单例模式?

问题场景:全局状态管理混乱

假设你在开发一个电商应用,需要管理用户登录状态和购物车:

❌ 不使用单例模式的痛点
javascript 复制代码
// UserManager.js - 每次导入都创建新实例
class UserManager {
  constructor() {
    this.currentUser = null;
    this.isLoggedIn = false;
  }

  login(user) {
    this.currentUser = user;
    this.isLoggedIn = true;
  }

  getUser() {
    return this.currentUser;
  }
}

export default UserManager;
javascript 复制代码
// Header.jsx - 创建了实例A
import UserManager from './UserManager';

function Header() {
  const userManager = new UserManager(); // 实例A
  const user = userManager.getUser(); // null,因为是新实例!

  return <div>欢迎, {user?.name || '游客'}</div>;
}
javascript 复制代码
// LoginPage.jsx - 创建了实例B
import UserManager from './UserManager';

function LoginPage() {
  const userManager = new UserManager(); // 实例B

  const handleLogin = () => {
    userManager.login({ name: '张三', id: 1 });
    // 实例B保存了用户信息,但实例A不知道!
  };

  return <button onClick={handleLogin}>登录</button>;
}

问题

  • 每个组件创建自己的 UserManager 实例
  • 在 LoginPage 登录后,Header 仍然显示"游客"
  • 状态不同步,数据孤岛

✅ 使用单例模式解决
javascript 复制代码
// UserManager.js - 单例实现
class UserManager {
  // 私有静态实例
  static instance = null;

  constructor() {
    // 如果实例已存在,返回已有实例
    if (UserManager.instance) {
      return UserManager.instance;
    }

    this.currentUser = null;
    this.isLoggedIn = false;

    // 保存实例
    UserManager.instance = this;
  }

  login(user) {
    this.currentUser = user;
    this.isLoggedIn = true;
    console.log('用户已登录:', user.name);
  }

  logout() {
    this.currentUser = null;
    this.isLoggedIn = false;
  }

  getUser() {
    return this.currentUser;
  }

  // 静态方法获取实例
  static getInstance() {
    if (!UserManager.instance) {
      UserManager.instance = new UserManager();
    }
    return UserManager.instance;
  }
}

// 导出单例实例
export default UserManager.getInstance();
javascript 复制代码
// Header.jsx - 使用单例
import userManager from './UserManager';

function Header() {
  const [user, setUser] = React.useState(userManager.getUser());

  React.useEffect(() => {
    // 监听用户状态变化
    const interval = setInterval(() => {
      setUser(userManager.getUser());
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <div>欢迎, {user?.name || '游客'}</div>;
}
javascript 复制代码
// LoginPage.jsx - 使用同一个单例
import userManager from './UserManager';

function LoginPage() {
  const handleLogin = () => {
    userManager.login({ name: '张三', id: 1 });
    // Header 会立即感知到用户登录!
  };

  return <button onClick={handleLogin}>登录</button>;
}

效果

  • ✅ 全局只有一个 UserManager 实例
  • ✅ 所有组件共享同一个状态
  • ✅ 登录后所有组件立即同步

🎯 更优雅的单例实现方式

方式1:ES6 模块天然单例
javascript 复制代码
// userStore.js - 利用ES6模块特性
class UserStore {
  constructor() {
    this.currentUser = null;
    this.listeners = [];
  }

  login(user) {
    this.currentUser = user;
    this.notify();
  }

  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }

  notify() {
    this.listeners.forEach(listener => listener(this.currentUser));
  }
}

// 直接导出实例 - ES6 模块只会执行一次
export default new UserStore();
javascript 复制代码
// 在React中使用
import userStore from './userStore';

function Header() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    return userStore.subscribe(setUser);
  }, []);

  return <div>欢迎, {user?.name || '游客'}</div>;
}
方式2:使用闭包实现单例
javascript 复制代码
// configManager.js
const ConfigManager = (function() {
  let instance;
  let config = {};

  function init() {
    return {
      set(key, value) {
        config[key] = value;
      },
      get(key) {
        return config[key];
      },
      getAll() {
        return { ...config };
      }
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = init();
      }
      return instance;
    }
  };
})();

export default ConfigManager.getInstance();

🏗️ 真实业务场景

场景1:Axios 实例管理
javascript 复制代码
// api/request.js - HTTP 客户端单例
import axios from 'axios';

class ApiClient {
  constructor() {
    if (ApiClient.instance) {
      return ApiClient.instance;
    }

    this.client = axios.create({
      baseURL: process.env.REACT_APP_API_URL,
      timeout: 10000,
    });

    // 请求拦截器
    this.client.interceptors.request.use(config => {
      const token = localStorage.getItem('token');
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // 响应拦截器
    this.client.interceptors.response.use(
      response => response.data,
      error => {
        if (error.response?.status === 401) {
          // 全局处理未授权
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );

    ApiClient.instance = this;
  }

  get(url, config) {
    return this.client.get(url, config);
  }

  post(url, data, config) {
    return this.client.post(url, data, config);
  }
}

export default new ApiClient();
javascript 复制代码
// 在组件中使用
import apiClient from './api/request';

function ProductList() {
  const [products, setProducts] = React.useState([]);

  React.useEffect(() => {
    apiClient.get('/products').then(setProducts);
  }, []);

  return <div>{products.map(p => <div key={p.id}>{p.name}</div>)}</div>;
}
场景2:日志管理器
javascript 复制代码
// logger.js
class Logger {
  constructor() {
    if (Logger.instance) {
      return Logger.instance;
    }

    this.logs = [];
    this.maxLogs = 1000;
    Logger.instance = this;
  }

  log(level, message, data) {
    const logEntry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      data,
    };

    this.logs.push(logEntry);

    // 限制日志数量
    if (this.logs.length > this.maxLogs) {
      this.logs.shift();
    }

    // 开发环境打印到控制台
    if (process.env.NODE_ENV === 'development') {
      console[level](message, data);
    }

    // 生产环境发送到服务器
    if (process.env.NODE_ENV === 'production' && level === 'error') {
      this.sendToServer(logEntry);
    }
  }

  error(message, data) {
    this.log('error', message, data);
  }

  info(message, data) {
    this.log('info', message, data);
  }

  sendToServer(logEntry) {
    // 发送到日志服务器
    fetch('/api/logs', {
      method: 'POST',
      body: JSON.stringify(logEntry),
    });
  }

  getLogs() {
    return [...this.logs];
  }
}

export default new Logger();

🎨 在主流框架中的应用

Redux Store - 单例模式典范
javascript 复制代码
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

// Redux Store 是单例
const store = createStore(rootReducer);

export default store;
javascript 复制代码
// App.jsx
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}> {/* 全局共享同一个 store */}
      <YourApp />
    </Provider>
  );
}
Vue Router - 单例实例
javascript 复制代码
// router.js
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

// 创建单例路由实例
const router = new VueRouter({
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
  ]
});

export default router;

⚖️ 优缺点分析

✅ 优点
  1. 唯一实例:确保全局只有一个实例,节省内存
  2. 全局访问:提供全局访问点,方便调用
  3. 延迟初始化:可以在首次使用时才创建实例
  4. 状态共享:多个模块共享同一状态
❌ 缺点
  1. 全局污染:相当于全局变量,可能导致命名冲突
  2. 难以测试:单例在测试中难以 mock
  3. 隐藏依赖:代码依赖关系不明显
  4. 违反单一职责:类既管理自己的职责,又管理实例创建

📋 何时使用单例模式

✅ 适合使用的场景
  • 全局配置管理(Config)
  • 日志管理器(Logger)
  • 缓存管理(Cache)
  • HTTP 客户端(Axios 实例)
  • 状态管理器(Redux Store, Vuex Store)
  • 数据库连接池
  • 路由实例
❌ 不适合使用的场景
  • 需要创建多个实例的对象
  • 需要频繁创建和销毁的对象
  • 有复杂继承关系的类
  • 需要在不同上下文中使用不同配置的对象

🎓 单例模式 vs React Context

在 React 中,我们更推荐使用 Context 而不是传统单例:

javascript 复制代码
// 使用 Context 替代单例
import React from 'react';

const UserContext = React.createContext(null);

export function UserProvider({ children }) {
  const [user, setUser] = React.useState(null);

  const login = (userData) => {
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <UserContext.Provider value={{ user, login, logout }}>
      {children}
    </UserContext.Provider>
  );
}

export const useUser = () => React.useContext(UserContext);
javascript 复制代码
// 使用
function Header() {
  const { user, logout } = useUser();
  return <div>{user?.name} <button onClick={logout}>退出</button></div>;
}

Context 的优势

  • ✅ 更符合 React 数据流
  • ✅ 自动触发组件重渲染
  • ✅ 更易于测试(可以包裹自定义 Provider)
  • ✅ 支持多个上下文并存

工厂模式

💡 模式定义

定义一个创建对象的接口,让子类决定实例化哪个类。工厂模式将对象的创建延迟到子类。

🤔 为什么需要工厂模式?

问题场景:根据类型创建不同组件

假设你在开发一个表单生成器,需要根据配置动态生成不同类型的表单项:

❌ 不使用工厂模式的痛点
javascript 复制代码
// FormItem.jsx - 充满 if-else 的组件
function FormItem({ type, config }) {
  // 大量 if-else 判断
  if (type === 'text') {
    return <input type="text" {...config} />;
  } else if (type === 'number') {
    return <input type="number" {...config} />;
  } else if (type === 'select') {
    return (
      <select {...config}>
        {config.options.map(opt => (
          <option key={opt.value} value={opt.value}>{opt.label}</option>
        ))}
      </select>
    );
  } else if (type === 'date') {
    return <input type="date" {...config} />;
  } else if (type === 'textarea') {
    return <textarea {...config} />;
  } else if (type === 'checkbox') {
    return <input type="checkbox" {...config} />;
  } else if (type === 'radio') {
    return (
      <div>
        {config.options.map(opt => (
          <label key={opt.value}>
            <input type="radio" value={opt.value} {...config} />
            {opt.label}
          </label>
        ))}
      </div>
    );
  } else if (type === 'file') {
    return <input type="file" {...config} />;
  }

  return null;
}

问题

  • 代码臃肿,难以维护
  • 新增类型需要修改主组件(违反开闭原则)
  • 每种类型的逻辑耦合在一起
  • 无法独立测试每种类型

✅ 使用工厂模式解决
javascript 复制代码
// formComponents/TextInput.jsx
export function TextInput({ value, onChange, placeholder }) {
  return (
    <input
      type="text"
      value={value}
      onChange={onChange}
      placeholder={placeholder}
    />
  );
}

// formComponents/SelectInput.jsx
export function SelectInput({ value, onChange, options }) {
  return (
    <select value={value} onChange={onChange}>
      <option value="">请选择</option>
      {options.map(opt => (
        <option key={opt.value} value={opt.value}>
          {opt.label}
        </option>
      ))}
    </select>
  );
}

// formComponents/DateInput.jsx
export function DateInput({ value, onChange }) {
  return <input type="date" value={value} onChange={onChange} />;
}

// ... 其他组件类似
javascript 复制代码
// FormComponentFactory.js - 工厂类
import { TextInput } from './formComponents/TextInput';
import { SelectInput } from './formComponents/SelectInput';
import { DateInput } from './formComponents/DateInput';
import { TextareaInput } from './formComponents/TextareaInput';
import { CheckboxInput } from './formComponents/CheckboxInput';
import { RadioInput } from './formComponents/RadioInput';

class FormComponentFactory {
  constructor() {
    // 注册所有表单组件
    this.components = {
      text: TextInput,
      select: SelectInput,
      date: DateInput,
      textarea: TextareaInput,
      checkbox: CheckboxInput,
      radio: RadioInput,
    };
  }

  // 创建组件
  create(type) {
    const Component = this.components[type];

    if (!Component) {
      console.warn(`Unknown form component type: ${type}`);
      return null;
    }

    return Component;
  }

  // 注册新组件(支持扩展)
  register(type, Component) {
    this.components[type] = Component;
  }

  // 批量注册
  registerMultiple(componentsMap) {
    Object.assign(this.components, componentsMap);
  }
}

export default new FormComponentFactory();
javascript 复制代码
// FormItem.jsx - 简洁的组件
import React from 'react';
import formFactory from './FormComponentFactory';

function FormItem({ type, value, onChange, ...config }) {
  const Component = formFactory.create(type);

  if (!Component) {
    return <div>不支持的表单类型: {type}</div>;
  }

  return (
    <div className="form-item">
      <label>{config.label}</label>
      <Component value={value} onChange={onChange} {...config} />
    </div>
  );
}

export default FormItem;
javascript 复制代码
// 使用示例
function UserForm() {
  const [formData, setFormData] = React.useState({
    name: '',
    age: '',
    gender: '',
    birthday: '',
  });

  const formConfig = [
    { type: 'text', name: 'name', label: '姓名' },
    { type: 'number', name: 'age', label: '年龄' },
    {
      type: 'select',
      name: 'gender',
      label: '性别',
      options: [
        { value: 'male', label: '男' },
        { value: 'female', label: '女' },
      ]
    },
    { type: 'date', name: 'birthday', label: '生日' },
  ];

  const handleChange = (name, value) => {
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  return (
    <form>
      {formConfig.map(config => (
        <FormItem
          key={config.name}
          {...config}
          value={formData[config.name]}
          onChange={(e) => handleChange(config.name, e.target.value)}
        />
      ))}
    </form>
  );
}

效果

  • ✅ 每种表单组件独立维护
  • ✅ 新增类型只需注册,不修改核心代码
  • ✅ 代码清晰,易于测试
  • ✅ 支持动态扩展

🎯 高级:抽象工厂模式

当你需要创建一系列相关对象时:

javascript 复制代码
// 不同主题的按钮工厂
class LightThemeFactory {
  createButton() {
    return function Button({ children, onClick }) {
      return (
        <button
          onClick={onClick}
          style={{
            background: '#fff',
            color: '#333',
            border: '1px solid #ddd',
          }}
        >
          {children}
        </button>
      );
    };
  }

  createInput() {
    return function Input(props) {
      return (
        <input
          {...props}
          style={{
            background: '#fff',
            color: '#333',
            border: '1px solid #ddd',
          }}
        />
      );
    };
  }
}

class DarkThemeFactory {
  createButton() {
    return function Button({ children, onClick }) {
      return (
        <button
          onClick={onClick}
          style={{
            background: '#333',
            color: '#fff',
            border: '1px solid #555',
          }}
        >
          {children}
        </button>
      );
    };
  }

  createInput() {
    return function Input(props) {
      return (
        <input
          {...props}
          style={{
            background: '#333',
            color: '#fff',
            border: '1px solid #555',
          }}
        />
      );
    };
  }
}

// 使用
function App() {
  const [theme, setTheme] = React.useState('light');

  // 根据主题选择工厂
  const factory = theme === 'light'
    ? new LightThemeFactory()
    : new DarkThemeFactory();

  const Button = factory.createButton();
  const Input = factory.createInput();

  return (
    <div>
      <Button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </Button>
      <Input placeholder="输入内容" />
    </div>
  );
}

🏗️ 真实业务场景

场景1:通知组件工厂
javascript 复制代码
// notifications/SuccessNotification.jsx
export function SuccessNotification({ message, onClose }) {
  return (
    <div style={{ background: '#52c41a', color: '#fff', padding: '12px' }}>
      ✓ {message}
      <button onClick={onClose}>×</button>
    </div>
  );
}

// notifications/ErrorNotification.jsx
export function ErrorNotification({ message, onClose }) {
  return (
    <div style={{ background: '#ff4d4f', color: '#fff', padding: '12px' }}>
      ✕ {message}
      <button onClick={onClose}>×</button>
    </div>
  );
}

// notifications/WarningNotification.jsx
export function WarningNotification({ message, onClose }) {
  return (
    <div style={{ background: '#faad14', color: '#fff', padding: '12px' }}>
      ⚠ {message}
      <button onClick={onClose}>×</button>
    </div>
  );
}
javascript 复制代码
// NotificationFactory.js
import { SuccessNotification } from './notifications/SuccessNotification';
import { ErrorNotification } from './notifications/ErrorNotification';
import { WarningNotification } from './notifications/WarningNotification';

class NotificationFactory {
  constructor() {
    this.types = {
      success: SuccessNotification,
      error: ErrorNotification,
      warning: WarningNotification,
    };
  }

  create(type, props) {
    const Component = this.types[type];
    if (!Component) return null;
    return <Component {...props} />;
  }
}

export default new NotificationFactory();
javascript 复制代码
// useNotification hook
import React from 'react';
import notificationFactory from './NotificationFactory';

export function useNotification() {
  const [notifications, setNotifications] = React.useState([]);

  const show = (type, message, duration = 3000) => {
    const id = Date.now();
    const notification = {
      id,
      type,
      message,
    };

    setNotifications(prev => [...prev, notification]);

    // 自动关闭
    if (duration > 0) {
      setTimeout(() => {
        setNotifications(prev => prev.filter(n => n.id !== id));
      }, duration);
    }
  };

  const close = (id) => {
    setNotifications(prev => prev.filter(n => n.id !== id));
  };

  const NotificationContainer = () => (
    <div style={{ position: 'fixed', top: 20, right: 20, zIndex: 9999 }}>
      {notifications.map(n => (
        <div key={n.id} style={{ marginBottom: 10 }}>
          {notificationFactory.create(n.type, {
            message: n.message,
            onClose: () => close(n.id),
          })}
        </div>
      ))}
    </div>
  );

  return {
    success: (msg) => show('success', msg),
    error: (msg) => show('error', msg),
    warning: (msg) => show('warning', msg),
    NotificationContainer,
  };
}
javascript 复制代码
// 使用
function App() {
  const notification = useNotification();

  return (
    <div>
      <button onClick={() => notification.success('保存成功!')}>
        测试成功通知
      </button>
      <button onClick={() => notification.error('保存失败!')}>
        测试错误通知
      </button>
      <button onClick={() => notification.warning('请注意!')}>
        测试警告通知
      </button>

      <notification.NotificationContainer />
    </div>
  );
}
场景2:图表组件工厂
javascript 复制代码
// ChartFactory.js
import { LineChart } from './charts/LineChart';
import { BarChart } from './charts/BarChart';
import { PieChart } from './charts/PieChart';

class ChartFactory {
  static create(type, data, options) {
    switch(type) {
      case 'line':
        return <LineChart data={data} options={options} />;
      case 'bar':
        return <BarChart data={data} options={options} />;
      case 'pie':
        return <PieChart data={data} options={options} />;
      default:
        return <div>不支持的图表类型</div>;
    }
  }
}

export default ChartFactory;
javascript 复制代码
// Dashboard.jsx
function Dashboard() {
  const chartsConfig = [
    { type: 'line', data: salesData, title: '销售趋势' },
    { type: 'bar', data: userGrowth, title: '用户增长' },
    { type: 'pie', data: marketShare, title: '市场份额' },
  ];

  return (
    <div className="dashboard">
      {chartsConfig.map((config, index) => (
        <div key={index} className="chart-container">
          <h3>{config.title}</h3>
          {ChartFactory.create(config.type, config.data)}
        </div>
      ))}
    </div>
  );
}

🎨 在主流框架中的应用

React.createElement - 工厂模式核心
javascript 复制代码
// React 内部使用工厂模式创建元素
React.createElement('div', { className: 'box' }, 'Hello');

// JSX 本质上就是工厂模式
<div className="box">Hello</div>
// ↓ 编译后
React.createElement('div', { className: 'box' }, 'Hello');
Vue 3 h 函数
javascript 复制代码
import { h } from 'vue';

// h 函数是工厂函数
export default {
  render() {
    return h('div', { class: 'box' }, 'Hello');
  }
}

⚖️ 优缺点分析

✅ 优点
  1. 解耦:对象创建和使用分离
  2. 扩展性:新增类型不需要修改现有代码
  3. 统一管理:集中管理对象创建逻辑
  4. 易于测试:可以 mock 工厂返回测试对象
❌ 缺点
  1. 增加复杂度:引入额外的工厂类
  2. 类膨胀:每个类型都需要一个类/组件
  3. 间接性:增加了代码的间接层次

📋 何时使用工厂模式

✅ 适合使用的场景
  • 动态表单生成
  • 根据配置创建不同组件
  • 通知/消息组件
  • 图表库
  • 路由配置转组件
  • 插件系统
❌ 不适合使用的场景
  • 只有 1-2 种类型的情况
  • 类型固定不会扩展
  • 创建逻辑非常简单
  • 直接创建更清晰的场景

依赖注入模式

💡 模式定义

依赖注入(Dependency Injection, DI)是一种实现控制反转(IoC)的设计模式。它将对象的依赖关系从对象内部转移到外部容器,由外部负责创建和注入依赖。

🤔 控制反转 vs 依赖注入

arduino 复制代码
控制反转(IoC - Inversion of Control)
  ↓ 这是一种设计原则/思想
  "不要自己创建依赖,让别人给你"
  ↓ 具体实现方式之一
依赖注入(DI - Dependency Injection)
  ↓ 这是一种具体的设计模式
  "通过构造函数、方法、属性注入依赖"

🤔 为什么需要依赖注入?

问题场景:组件依赖难以测试和复用
❌ 不使用依赖注入的痛点
javascript 复制代码
// UserService.js - 硬编码依赖
class UserService {
  constructor() {
    // 直接在内部创建依赖(硬编码)
    this.apiClient = axios.create({
      baseURL: 'https://api.example.com',
      timeout: 5000,
    });

    this.logger = console; // 直接依赖 console
    this.cache = new Map(); // 直接创建缓存
  }

  async getUser(id) {
    // 检查缓存
    if (this.cache.has(id)) {
      this.logger.log('从缓存获取用户:', id);
      return this.cache.get(id);
    }

    // 请求 API
    try {
      this.logger.log('从 API 获取用户:', id);
      const response = await this.apiClient.get(`/users/${id}`);
      this.cache.set(id, response.data);
      return response.data;
    } catch (error) {
      this.logger.error('获取用户失败:', error);
      throw error;
    }
  }
}

export default new UserService();
javascript 复制代码
// UserProfile.jsx - 难以测试的组件
import userService from './UserService';

function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    // 直接依赖全局的 userService
    userService.getUser(userId).then(setUser);
  }, [userId]);

  return user ? <div>{user.name}</div> : <div>加载中...</div>;
}

问题

  • 难以测试:无法 mock apiClient、logger、cache
  • 难以复用:在不同环境(开发/测试/生产)使用不同配置很困难
  • 耦合度高:UserService 必须使用特定的依赖
  • 不灵活:更换依赖需要修改 UserService 源码

✅ 使用依赖注入解决
javascript 复制代码
// UserService.js - 依赖通过构造函数注入
class UserService {
  constructor(apiClient, logger, cache) {
    // 依赖从外部注入
    this.apiClient = apiClient;
    this.logger = logger;
    this.cache = cache;
  }

  async getUser(id) {
    if (this.cache.has(id)) {
      this.logger.log('从缓存获取用户:', id);
      return this.cache.get(id);
    }

    try {
      this.logger.log('从 API 获取用户:', id);
      const response = await this.apiClient.get(`/users/${id}`);
      this.cache.set(id, response.data);
      return response.data;
    } catch (error) {
      this.logger.error('获取用户失败:', error);
      throw error;
    }
  }
}

export default UserService;
javascript 复制代码
// di-container.js - DI 容器
import axios from 'axios';
import UserService from './UserService';
import Logger from './Logger';

class DIContainer {
  constructor() {
    this.services = new Map();
  }

  // 注册服务
  register(name, factory) {
    this.services.set(name, factory);
  }

  // 获取服务(自动创建实例)
  get(name) {
    const factory = this.services.get(name);
    if (!factory) {
      throw new Error(`Service ${name} not found`);
    }
    return factory(this);
  }
}

// 创建容器实例
const container = new DIContainer();

// 注册依赖
container.register('apiClient', () => {
  return axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    timeout: 5000,
  });
});

container.register('logger', () => {
  return {
    log: (...args) => console.log('[LOG]', ...args),
    error: (...args) => console.error('[ERROR]', ...args),
  };
});

container.register('cache', () => {
  return new Map();
});

// 注册 UserService,自动注入依赖
container.register('userService', (container) => {
  return new UserService(
    container.get('apiClient'),
    container.get('logger'),
    container.get('cache')
  );
});

export default container;
javascript 复制代码
// UserProfile.jsx - 通过 props 注入依赖
function UserProfile({ userId, userService }) {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    userService.getUser(userId).then(setUser);
  }, [userId, userService]);

  return user ? <div>{user.name}</div> : <div>加载中...</div>;
}

// App.jsx - 在顶层注入依赖
import container from './di-container';

function App() {
  const userService = container.get('userService');

  return <UserProfile userId={1} userService={userService} />;
}
javascript 复制代码
// UserProfile.test.js - 轻松测试!
import { render } from '@testing-library/react';
import UserProfile from './UserProfile';

test('显示用户名称', async () => {
  // Mock userService
  const mockUserService = {
    getUser: jest.fn().mockResolvedValue({ name: '张三' }),
  };

  const { findByText } = render(
    <UserProfile userId={1} userService={mockUserService} />
  );

  expect(await findByText('张三')).toBeInTheDocument();
  expect(mockUserService.getUser).toHaveBeenCalledWith(1);
});

效果

  • 易于测试:可以轻松 mock 依赖
  • 灵活配置:开发/测试/生产使用不同依赖
  • 松耦合:UserService 不依赖具体实现
  • 可复用:同一个 UserService 可用于不同场景

🎯 React 中的依赖注入模式

方式1:React Context(推荐)
javascript 复制代码
// ServiceContext.jsx - 使用 Context 实现 DI
import React from 'react';
import container from './di-container';

const ServiceContext = React.createContext(null);

export function ServiceProvider({ children }) {
  const services = {
    userService: container.get('userService'),
    productService: container.get('productService'),
    cartService: container.get('cartService'),
  };

  return (
    <ServiceContext.Provider value={services}>
      {children}
    </ServiceContext.Provider>
  );
}

// 自定义 Hook
export function useService(serviceName) {
  const services = React.useContext(ServiceContext);
  if (!services) {
    throw new Error('useService must be used within ServiceProvider');
  }
  return services[serviceName];
}
javascript 复制代码
// App.jsx
import { ServiceProvider } from './ServiceContext';

function App() {
  return (
    <ServiceProvider>
      <UserProfile userId={1} />
      <ProductList />
      <ShoppingCart />
    </ServiceProvider>
  );
}
javascript 复制代码
// UserProfile.jsx - 使用注入的服务
import { useService } from './ServiceContext';

function UserProfile({ userId }) {
  const userService = useService('userService');
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    userService.getUser(userId).then(setUser);
  }, [userId, userService]);

  return user ? <div>{user.name}</div> : <div>加载中...</div>;
}
方式2:高阶组件(HOC)注入
javascript 复制代码
// withServices.jsx - HOC 实现 DI
import React from 'react';
import container from './di-container';

export function withServices(...serviceNames) {
  return function(Component) {
    return function WithServicesComponent(props) {
      const services = {};
      serviceNames.forEach(name => {
        services[name] = container.get(name);
      });

      return <Component {...props} {...services} />;
    };
  };
}
javascript 复制代码
// UserProfile.jsx - 使用 HOC 注入
function UserProfile({ userId, userService }) {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    userService.getUser(userId).then(setUser);
  }, [userId, userService]);

  return user ? <div>{user.name}</div> : <div>加载中...</div>;
}

export default withServices('userService')(UserProfile);

🏗️ 真实业务场景

场景1:可测试的数据层
javascript 复制代码
// repositories/UserRepository.js
class UserRepository {
  constructor(apiClient) {
    this.apiClient = apiClient;
  }

  async findById(id) {
    const response = await this.apiClient.get(`/users/${id}`);
    return response.data;
  }

  async findAll() {
    const response = await this.apiClient.get('/users');
    return response.data;
  }

  async create(userData) {
    const response = await this.apiClient.post('/users', userData);
    return response.data;
  }
}

export default UserRepository;
javascript 复制代码
// services/UserService.js
class UserService {
  constructor(userRepository, logger) {
    this.repository = userRepository;
    this.logger = logger;
  }

  async getUserProfile(id) {
    try {
      this.logger.log('获取用户资料:', id);
      const user = await this.repository.findById(id);

      // 业务逻辑:格式化用户数据
      return {
        ...user,
        displayName: `${user.firstName} ${user.lastName}`,
        isActive: user.status === 'active',
      };
    } catch (error) {
      this.logger.error('获取用户资料失败:', error);
      throw error;
    }
  }
}

export default UserService;
javascript 复制代码
// di-container.js
import axios from 'axios';
import UserRepository from './repositories/UserRepository';
import UserService from './services/UserService';

const container = new DIContainer();

// 注册 API 客户端
container.register('apiClient', () =>
  axios.create({
    baseURL: process.env.REACT_APP_API_URL,
  })
);

// 注册 Logger
container.register('logger', () => ({
  log: (...args) => console.log(...args),
  error: (...args) => console.error(...args),
}));

// 注册 Repository
container.register('userRepository', (c) =>
  new UserRepository(c.get('apiClient'))
);

// 注册 Service
container.register('userService', (c) =>
  new UserService(
    c.get('userRepository'),
    c.get('logger')
  )
);

export default container;
javascript 复制代码
// UserService.test.js - 完美的单元测试
import UserService from './UserService';

describe('UserService', () => {
  test('获取用户资料', async () => {
    // Mock 依赖
    const mockRepository = {
      findById: jest.fn().mockResolvedValue({
        id: 1,
        firstName: '张',
        lastName: '三',
        status: 'active',
      }),
    };

    const mockLogger = {
      log: jest.fn(),
      error: jest.fn(),
    };

    // 注入 mock 依赖
    const userService = new UserService(mockRepository, mockLogger);

    // 执行测试
    const user = await userService.getUserProfile(1);

    // 断言
    expect(user.displayName).toBe('张 三');
    expect(user.isActive).toBe(true);
    expect(mockRepository.findById).toHaveBeenCalledWith(1);
    expect(mockLogger.log).toHaveBeenCalled();
  });
});
场景2:环境配置注入
javascript 复制代码
// config.js - 不同环境的配置
export const developmentConfig = {
  apiUrl: 'http://localhost:3000',
  logLevel: 'debug',
  enableMock: true,
};

export const productionConfig = {
  apiUrl: 'https://api.production.com',
  logLevel: 'error',
  enableMock: false,
};

export const testConfig = {
  apiUrl: 'http://test-api.com',
  logLevel: 'silent',
  enableMock: true,
};
javascript 复制代码
// di-container.js - 根据环境注入不同配置
const env = process.env.NODE_ENV;
const configs = {
  development: developmentConfig,
  production: productionConfig,
  test: testConfig,
};

const config = configs[env] || developmentConfig;

container.register('config', () => config);

container.register('apiClient', (c) => {
  const config = c.get('config');
  return axios.create({
    baseURL: config.apiUrl,
  });
});

container.register('logger', (c) => {
  const config = c.get('config');

  if (config.logLevel === 'silent') {
    return {
      log: () => {},
      error: () => {},
    };
  }

  return {
    log: (...args) => console.log(...args),
    error: (...args) => console.error(...args),
  };
});

🎨 在主流框架中的应用

Angular - 内置 DI 系统
typescript 复制代码
// Angular 的依赖注入是框架核心
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root' // 单例注入
})
export class UserService {
  constructor(
    private http: HttpClient, // 自动注入 HttpClient
    private logger: LoggerService // 自动注入 LoggerService
  ) {}

  getUser(id: number) {
    return this.http.get(`/users/${id}`);
  }
}

// 组件中使用
export class UserComponent {
  constructor(private userService: UserService) {} // 自动注入
}
React Context - DI 的实现
javascript 复制代码
// React Context 本质上就是依赖注入
const ThemeContext = React.createContext();

function App() {
  return (
    <ThemeContext.Provider value={{ color: 'blue' }}>
      <Header />
    </ThemeContext.Provider>
  );
}

function Header() {
  const theme = React.useContext(ThemeContext); // 注入主题
  return <div style={{ color: theme.color }}>Header</div>;
}
Vue 3 provide/inject
javascript 复制代码
// Vue 3 的 provide/inject 实现 DI
import { provide, inject } from 'vue';

// 父组件提供依赖
export default {
  setup() {
    const userService = new UserService();
    provide('userService', userService);
  }
}

// 子组件注入依赖
export default {
  setup() {
    const userService = inject('userService');
    return { userService };
  }
}

⚖️ 优缺点分析

✅ 优点
  1. 易于测试:可以轻松 mock 依赖
  2. 松耦合:组件不依赖具体实现
  3. 灵活配置:不同环境使用不同依赖
  4. 可维护性:依赖关系清晰明确
  5. 可复用性:同一组件可用于不同场景
❌ 缺点
  1. 学习曲线:需要理解 IoC 和 DI 概念
  2. 复杂度:增加了代码层次
  3. 过度设计:简单项目可能不需要
  4. 性能开销:DI 容器有一定性能开销

📋 何时使用依赖注入

✅ 适合使用的场景
  • 大型应用,需要管理复杂依赖关系
  • 需要编写大量单元测试
  • 多环境部署(开发/测试/生产)
  • 需要运行时替换依赖
  • 插件系统、可扩展架构
❌ 不适合使用的场景
  • 小型项目,依赖关系简单
  • 不需要测试的原型项目
  • 性能要求极高的场景
  • 团队不熟悉 DI 概念

🎓 依赖注入最佳实践

javascript 复制代码
// ✅ 好的实践:通过构造函数注入
class OrderService {
  constructor(paymentService, emailService) {
    this.paymentService = paymentService;
    this.emailService = emailService;
  }

  async createOrder(orderData) {
    const payment = await this.paymentService.process(orderData);
    await this.emailService.sendConfirmation(orderData);
    return payment;
  }
}

// ❌ 坏的实践:在内部直接创建依赖
class OrderService {
  async createOrder(orderData) {
    const paymentService = new PaymentService(); // 硬编码!
    const emailService = new EmailService(); // 硬编码!

    const payment = await paymentService.process(orderData);
    await emailService.sendConfirmation(orderData);
    return payment;
  }
}

建造者模式

💡 模式定义

建造者模式(Builder Pattern)将复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

🤔 为什么需要建造者模式?

问题场景:构建复杂配置对象

假设你在开发一个 HTTP 请求库,需要支持各种配置选项:

❌ 不使用建造者模式的痛点
javascript 复制代码
// HttpClient.js - 构造函数参数过多
class HttpClient {
  constructor(
    baseURL,
    timeout,
    headers,
    withCredentials,
    responseType,
    maxRedirects,
    validateStatus,
    transformRequest,
    transformResponse,
    cancelToken
  ) {
    this.baseURL = baseURL;
    this.timeout = timeout;
    this.headers = headers;
    this.withCredentials = withCredentials;
    this.responseType = responseType;
    this.maxRedirects = maxRedirects;
    this.validateStatus = validateStatus;
    this.transformRequest = transformRequest;
    this.transformResponse = transformResponse;
    this.cancelToken = cancelToken;
  }

  request(url, options) {
    // ... 请求逻辑
  }
}

// 使用时非常混乱
const client = new HttpClient(
  'https://api.example.com', // baseURL
  5000,                       // timeout
  { 'Content-Type': 'application/json' }, // headers
  true,                       // withCredentials
  'json',                     // responseType
  5,                          // maxRedirects
  null,                       // validateStatus
  null,                       // transformRequest
  null,                       // transformResponse
  null                        // cancelToken
);

// 想要跳过某些参数?必须传 null 或 undefined!
const simpleClient = new HttpClient(
  'https://api.example.com',
  undefined, // 不想设置 timeout
  undefined, // 不想设置 headers
  undefined, // 不想设置 withCredentials
  'json'     // 只想设置 responseType
);

问题

  • 参数过多,难以记忆顺序
  • 可选参数必须传 undefined
  • 代码可读性差
  • 修改参数顺序会破坏所有调用

✅ 使用建造者模式解决
javascript 复制代码
// HttpClient.js - 使用建造者模式
class HttpClient {
  constructor(config) {
    this.config = config;
  }

  async request(url, options = {}) {
    const config = { ...this.config, ...options };

    try {
      const response = await fetch(config.baseURL + url, {
        method: config.method || 'GET',
        headers: config.headers,
        body: config.data ? JSON.stringify(config.data) : undefined,
        credentials: config.withCredentials ? 'include' : 'same-origin',
        signal: config.cancelToken?.signal,
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return config.responseType === 'json'
        ? await response.json()
        : await response.text();
    } catch (error) {
      console.error('Request failed:', error);
      throw error;
    }
  }

  get(url, config) {
    return this.request(url, { ...config, method: 'GET' });
  }

  post(url, data, config) {
    return this.request(url, { ...config, method: 'POST', data });
  }
}

// HttpClientBuilder.js - 建造者类
class HttpClientBuilder {
  constructor() {
    this.config = {
      baseURL: '',
      timeout: 5000,
      headers: {},
      withCredentials: false,
      responseType: 'json',
    };
  }

  // 链式调用方法
  setBaseURL(baseURL) {
    this.config.baseURL = baseURL;
    return this; // 返回 this 支持链式调用
  }

  setTimeout(timeout) {
    this.config.timeout = timeout;
    return this;
  }

  setHeaders(headers) {
    this.config.headers = { ...this.config.headers, ...headers };
    return this;
  }

  addHeader(key, value) {
    this.config.headers[key] = value;
    return this;
  }

  withCredentials(enabled = true) {
    this.config.withCredentials = enabled;
    return this;
  }

  setResponseType(type) {
    this.config.responseType = type;
    return this;
  }

  // 构建最终对象
  build() {
    return new HttpClient(this.config);
  }
}

export { HttpClient, HttpClientBuilder };
javascript 复制代码
// 使用建造者模式 - 清晰易读!
import { HttpClientBuilder } from './HttpClient';

const client = new HttpClientBuilder()
  .setBaseURL('https://api.example.com')
  .setTimeout(10000)
  .addHeader('Content-Type', 'application/json')
  .addHeader('Authorization', 'Bearer token123')
  .withCredentials()
  .setResponseType('json')
  .build();

// 只设置需要的参数
const simpleClient = new HttpClientBuilder()
  .setBaseURL('https://api.example.com')
  .setResponseType('json')
  .build();

// 使用
client.get('/users').then(users => {
  console.log('用户列表:', users);
});

client.post('/users', { name: '张三', age: 25 }).then(user => {
  console.log('创建用户成功:', user);
});

效果

  • ✅ 代码清晰易读,参数含义明确
  • ✅ 链式调用,流畅的 API
  • ✅ 可选参数不需要传 undefined
  • ✅ 易于扩展新配置项

🎯 React 组件的建造者模式

javascript 复制代码
// Dialog.jsx - 对话框组件
function Dialog({ config }) {
  if (!config.visible) return null;

  return (
    <div className="dialog-overlay">
      <div className="dialog" style={{ width: config.width }}>
        {config.title && (
          <div className="dialog-header">
            <h3>{config.title}</h3>
            {config.closable && (
              <button onClick={config.onClose}>×</button>
            )}
          </div>
        )}

        <div className="dialog-body">
          {config.content}
        </div>

        {config.footer && (
          <div className="dialog-footer">
            {config.footer}
          </div>
        )}
      </div>
    </div>
  );
}

// DialogBuilder.js - 对话框建造者
class DialogBuilder {
  constructor() {
    this.config = {
      visible: false,
      title: '',
      content: null,
      width: 520,
      closable: true,
      footer: null,
      onClose: () => {},
      onOk: () => {},
      onCancel: () => {},
    };
  }

  setTitle(title) {
    this.config.title = title;
    return this;
  }

  setContent(content) {
    this.config.content = content;
    return this;
  }

  setWidth(width) {
    this.config.width = width;
    return this;
  }

  setVisible(visible) {
    this.config.visible = visible;
    return this;
  }

  setClosable(closable) {
    this.config.closable = closable;
    return this;
  }

  withFooter(footer) {
    this.config.footer = footer;
    return this;
  }

  // 预设样式
  asConfirm() {
    this.config.footer = (
      <div>
        <button onClick={this.config.onCancel}>取消</button>
        <button onClick={this.config.onOk}>确定</button>
      </div>
    );
    return this;
  }

  asAlert() {
    this.config.closable = false;
    this.config.footer = (
      <button onClick={this.config.onOk}>知道了</button>
    );
    return this;
  }

  onOk(callback) {
    this.config.onOk = callback;
    return this;
  }

  onCancel(callback) {
    this.config.onCancel = callback;
    return this;
  }

  onClose(callback) {
    this.config.onClose = callback;
    return this;
  }

  build() {
    return this.config;
  }
}

export { Dialog, DialogBuilder };
javascript 复制代码
// 使用示例
import { Dialog, DialogBuilder } from './Dialog';

function App() {
  const [dialogConfig, setDialogConfig] = React.useState(null);

  const showConfirmDialog = () => {
    const config = new DialogBuilder()
      .setTitle('确认删除')
      .setContent('确定要删除这条记录吗?')
      .setVisible(true)
      .asConfirm()
      .onOk(() => {
        console.log('用户点击了确定');
        setDialogConfig(null);
      })
      .onCancel(() => {
        console.log('用户点击了取消');
        setDialogConfig(null);
      })
      .build();

    setDialogConfig(config);
  };

  const showAlertDialog = () => {
    const config = new DialogBuilder()
      .setTitle('操作成功')
      .setContent('您的修改已保存')
      .setVisible(true)
      .asAlert()
      .onOk(() => setDialogConfig(null))
      .build();

    setDialogConfig(config);
  };

  const showCustomDialog = () => {
    const config = new DialogBuilder()
      .setTitle('自定义对话框')
      .setContent(<div>这是自定义内容</div>)
      .setWidth(800)
      .setVisible(true)
      .withFooter(
        <div>
          <button>自定义按钮1</button>
          <button>自定义按钮2</button>
        </div>
      )
      .onClose(() => setDialogConfig(null))
      .build();

    setDialogConfig(config);
  };

  return (
    <div>
      <button onClick={showConfirmDialog}>显示确认对话框</button>
      <button onClick={showAlertDialog}>显示提示对话框</button>
      <button onClick={showCustomDialog}>显示自定义对话框</button>

      {dialogConfig && <Dialog config={dialogConfig} />}
    </div>
  );
}

🏗️ 真实业务场景

场景1:SQL 查询构建器
javascript 复制代码
// QueryBuilder.js
class QueryBuilder {
  constructor() {
    this.query = {
      table: '',
      columns: ['*'],
      where: [],
      orderBy: [],
      limit: null,
      offset: null,
    };
  }

  from(table) {
    this.query.table = table;
    return this;
  }

  select(...columns) {
    this.query.columns = columns;
    return this;
  }

  where(condition, operator = '=', value) {
    this.query.where.push({ condition, operator, value });
    return this;
  }

  orderBy(column, direction = 'ASC') {
    this.query.orderBy.push({ column, direction });
    return this;
  }

  limit(count) {
    this.query.limit = count;
    return this;
  }

  offset(count) {
    this.query.offset = count;
    return this;
  }

  build() {
    let sql = `SELECT ${this.query.columns.join(', ')} FROM ${this.query.table}`;

    if (this.query.where.length > 0) {
      const conditions = this.query.where
        .map(w => `${w.condition} ${w.operator} '${w.value}'`)
        .join(' AND ');
      sql += ` WHERE ${conditions}`;
    }

    if (this.query.orderBy.length > 0) {
      const orders = this.query.orderBy
        .map(o => `${o.column} ${o.direction}`)
        .join(', ');
      sql += ` ORDER BY ${orders}`;
    }

    if (this.query.limit) {
      sql += ` LIMIT ${this.query.limit}`;
    }

    if (this.query.offset) {
      sql += ` OFFSET ${this.query.offset}`;
    }

    return sql;
  }
}

export default QueryBuilder;
javascript 复制代码
// 使用查询构建器
import QueryBuilder from './QueryBuilder';

const query1 = new QueryBuilder()
  .from('users')
  .select('id', 'name', 'email')
  .where('age', '>', 18)
  .where('status', '=', 'active')
  .orderBy('created_at', 'DESC')
  .limit(10)
  .build();

console.log(query1);
// SELECT id, name, email FROM users
// WHERE age > '18' AND status = 'active'
// ORDER BY created_at DESC
// LIMIT 10

const query2 = new QueryBuilder()
  .from('products')
  .where('category', '=', 'electronics')
  .orderBy('price', 'ASC')
  .build();

console.log(query2);
// SELECT * FROM products
// WHERE category = 'electronics'
// ORDER BY price ASC
场景2:表单验证器构建器
javascript 复制代码
// FormValidatorBuilder.js
class FormValidatorBuilder {
  constructor() {
    this.rules = [];
  }

  required(message = '此字段为必填项') {
    this.rules.push({
      type: 'required',
      message,
      validate: (value) => {
        return value !== null && value !== undefined && value !== '';
      },
    });
    return this;
  }

  minLength(length, message) {
    this.rules.push({
      type: 'minLength',
      message: message || `最少需要 ${length} 个字符`,
      validate: (value) => {
        return value && value.length >= length;
      },
    });
    return this;
  }

  maxLength(length, message) {
    this.rules.push({
      type: 'maxLength',
      message: message || `最多允许 ${length} 个字符`,
      validate: (value) => {
        return !value || value.length <= length;
      },
    });
    return this;
  }

  email(message = '请输入有效的邮箱地址') {
    this.rules.push({
      type: 'email',
      message,
      validate: (value) => {
        const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return !value || regex.test(value);
      },
    });
    return this;
  }

  pattern(regex, message = '格式不正确') {
    this.rules.push({
      type: 'pattern',
      message,
      validate: (value) => {
        return !value || regex.test(value);
      },
    });
    return this;
  }

  custom(validateFn, message) {
    this.rules.push({
      type: 'custom',
      message,
      validate: validateFn,
    });
    return this;
  }

  build() {
    return (value) => {
      for (const rule of this.rules) {
        if (!rule.validate(value)) {
          return { valid: false, message: rule.message };
        }
      }
      return { valid: true };
    };
  }
}

export default FormValidatorBuilder;
javascript 复制代码
// 使用验证器构建器
import FormValidatorBuilder from './FormValidatorBuilder';

// 用户名验证
const usernameValidator = new FormValidatorBuilder()
  .required('用户名不能为空')
  .minLength(3, '用户名至少3个字符')
  .maxLength(20, '用户名最多20个字符')
  .pattern(/^[a-zA-Z0-9_]+$/, '用户名只能包含字母、数字和下划线')
  .build();

// 邮箱验证
const emailValidator = new FormValidatorBuilder()
  .required('邮箱不能为空')
  .email()
  .build();

// 密码验证
const passwordValidator = new FormValidatorBuilder()
  .required('密码不能为空')
  .minLength(8, '密码至少8个字符')
  .custom(
    (value) => /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value),
    '密码必须包含大写字母、小写字母和数字'
  )
  .build();

// 使用
console.log(usernameValidator('ab'));        // { valid: false, message: '用户名至少3个字符' }
console.log(usernameValidator('abc123'));    // { valid: true }
console.log(emailValidator('test@test.com')); // { valid: true }
console.log(passwordValidator('12345678'));  // { valid: false, message: '密码必须包含...' }

🎨 在主流框架中的应用

Axios 配置 - 建造者模式
javascript 复制代码
// Axios 使用建造者模式配置请求
const axiosInstance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-Custom-Header': 'foobar' }
});

// 链式配置请求
axiosInstance
  .get('/users')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));
React Query Builder
javascript 复制代码
// React Query 的配置也类似建造者模式
const query = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  staleTime: 5000,
  cacheTime: 10000,
  retry: 3,
  retryDelay: 1000,
});

⚖️ 优缺点分析

✅ 优点
  1. 可读性强:链式调用,代码清晰
  2. 灵活组合:可选参数,随意组合
  3. 易于扩展:新增配置项不影响现有代码
  4. 封装复杂性:隐藏复杂的构建逻辑
  5. 不可变性:可以实现不可变对象
❌ 缺点
  1. 代码量增加:需要额外的建造者类
  2. 对象创建开销:多一层建造者对象
  3. 过度设计:简单对象不需要建造者

📋 何时使用建造者模式

✅ 适合使用的场景
  • 构造函数参数过多(>4个)
  • 有大量可选参数
  • 需要创建不可变对象
  • 构建过程复杂,需要多步骤
  • 需要创建多种配置的对象
❌ 不适合使用的场景
  • 对象结构简单(<4个参数)
  • 不需要灵活配置
  • 性能要求极高的场景
  • 对象创建非常频繁

🎓 建造者模式最佳实践

javascript 复制代码
// ✅ 好的实践:支持多种构建方式
class HttpClientBuilder {
  // 方式1:链式调用
  setBaseURL(url) {
    this.config.baseURL = url;
    return this;
  }

  // 方式2:批量配置
  configure(config) {
    Object.assign(this.config, config);
    return this;
  }

  // 方式3:预设配置
  asProduction() {
    this.config.baseURL = 'https://api.production.com';
    this.config.timeout = 10000;
    return this;
  }

  asDevelopment() {
    this.config.baseURL = 'http://localhost:3000';
    this.config.timeout = 30000;
    return this;
  }
}

// 使用
const client1 = new HttpClientBuilder()
  .setBaseURL('https://api.example.com')
  .setTimeout(5000)
  .build();

const client2 = new HttpClientBuilder()
  .asProduction()
  .build();

const client3 = new HttpClientBuilder()
  .configure({
    baseURL: 'https://api.example.com',
    timeout: 5000,
  })
  .build();

📝 总结

创建型模式对比

模式 核心目的 使用场景 优先级
单例模式 确保只有一个实例 全局配置、状态管理 ⭐⭐⭐⭐⭐
工厂模式 根据类型创建对象 动态组件、表单生成器 ⭐⭐⭐⭐⭐
依赖注入 解耦依赖关系 测试、多环境配置 ⭐⭐⭐⭐
建造者模式 构建复杂对象 链式API、配置对象 ⭐⭐⭐⭐

学习建议

  1. 单例模式:先掌握基本实现,再学习 React Context 替代方案
  2. 工厂模式:理解如何用组件注册表替代 if-else
  3. 依赖注入:重点理解 IoC 思想,在测试中实践
  4. 建造者模式:学习链式调用的优雅 API 设计

相关推荐
小林rush2 小时前
uni-app跨分包自定义组件引用解决方案
前端·javascript·vue.js
我的一行2 小时前
已有项目,接入pnpm + turbo
前端·vue.js
亮子AI2 小时前
【Svelte】怎样实现一个图片上传功能?
开发语言·前端·javascript·svelte
心.c2 小时前
为什么在 Vue 3 中 uni.createCanvasContext 画不出图?
前端·javascript·vue.js
咸鱼加辣2 小时前
【vue面试】ref和reactive
前端·javascript·vue.js
LYFlied2 小时前
【每日算法】LeetCode 104. 二叉树的最大深度
前端·算法·leetcode·面试·职场和发展
汤姆Tom2 小时前
前端转战后端:JavaScript 与 Java 对照学习指南(第五篇 —— 面向对象:类、接口与多态)
java·前端·后端
宁雨桥2 小时前
前端并发控制的多种实现方案与最佳实践
前端