第一部分:创建型模式
创建型模式关注对象的创建机制,让你的代码更灵活、更易维护
目录
- [单例模式 (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;
⚖️ 优缺点分析
✅ 优点
- 唯一实例:确保全局只有一个实例,节省内存
- 全局访问:提供全局访问点,方便调用
- 延迟初始化:可以在首次使用时才创建实例
- 状态共享:多个模块共享同一状态
❌ 缺点
- 全局污染:相当于全局变量,可能导致命名冲突
- 难以测试:单例在测试中难以 mock
- 隐藏依赖:代码依赖关系不明显
- 违反单一职责:类既管理自己的职责,又管理实例创建
📋 何时使用单例模式
✅ 适合使用的场景
- 全局配置管理(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');
}
}
⚖️ 优缺点分析
✅ 优点
- 解耦:对象创建和使用分离
- 扩展性:新增类型不需要修改现有代码
- 统一管理:集中管理对象创建逻辑
- 易于测试:可以 mock 工厂返回测试对象
❌ 缺点
- 增加复杂度:引入额外的工厂类
- 类膨胀:每个类型都需要一个类/组件
- 间接性:增加了代码的间接层次
📋 何时使用工厂模式
✅ 适合使用的场景
- 动态表单生成
- 根据配置创建不同组件
- 通知/消息组件
- 图表库
- 路由配置转组件
- 插件系统
❌ 不适合使用的场景
- 只有 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 };
}
}
⚖️ 优缺点分析
✅ 优点
- 易于测试:可以轻松 mock 依赖
- 松耦合:组件不依赖具体实现
- 灵活配置:不同环境使用不同依赖
- 可维护性:依赖关系清晰明确
- 可复用性:同一组件可用于不同场景
❌ 缺点
- 学习曲线:需要理解 IoC 和 DI 概念
- 复杂度:增加了代码层次
- 过度设计:简单项目可能不需要
- 性能开销: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,
});
⚖️ 优缺点分析
✅ 优点
- 可读性强:链式调用,代码清晰
- 灵活组合:可选参数,随意组合
- 易于扩展:新增配置项不影响现有代码
- 封装复杂性:隐藏复杂的构建逻辑
- 不可变性:可以实现不可变对象
❌ 缺点
- 代码量增加:需要额外的建造者类
- 对象创建开销:多一层建造者对象
- 过度设计:简单对象不需要建造者
📋 何时使用建造者模式
✅ 适合使用的场景
- 构造函数参数过多(>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、配置对象 | ⭐⭐⭐⭐ |
学习建议
- 单例模式:先掌握基本实现,再学习 React Context 替代方案
- 工厂模式:理解如何用组件注册表替代 if-else
- 依赖注入:重点理解 IoC 思想,在测试中实践
- 建造者模式:学习链式调用的优雅 API 设计