第二部分:结构型模式
结构型模式关注类和对象的组合,帮助你构建更灵活、更易维护的代码结构
目录
装饰器模式
💡 模式定义
装饰器模式(Decorator Pattern)允许在不改变对象自身的基础上,动态地给对象添加新的功能。它是通过创建包装对象来实现的。
🤔 为什么需要装饰器模式?
问题场景:给组件添加权限控制、日志、性能监控等功能
假设你有一个用户信息页面,现在需要添加权限控制、加载状态、错误边界等功能:
❌ 不使用装饰器模式的痛点
// UserProfile.jsx - 所有功能都耦合在一起
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [hasPermission, setHasPermission] = React.useState(false);
React.useEffect(() => {
// 权限检查逻辑
const checkPermission = async () => {
try {
const permission = await checkUserPermission();
setHasPermission(permission);
} catch (err) {
setError('权限检查失败');
}
};
// 数据加载逻辑
const loadUser = async () => {
if (!hasPermission) return;
try {
setLoading(true);
// 性能监控
const startTime = performance.now();
// 日志记录
console.log('开始加载用户:', userId);
const data = await fetchUser(userId);
setUser(data);
// 性能监控
const endTime = performance.now();
console.log(`加载用户耗时: ${endTime - startTime}ms`);
// 日志记录
console.log('用户加载成功:', data);
} catch (err) {
setError(err.message);
console.error('用户加载失败:', err);
} finally {
setLoading(false);
}
};
checkPermission().then(loadUser);
}, [userId, hasPermission]);
// 错误边界逻辑
if (error) {
return <div className="error">错误: {error}</div>;
}
// 权限检查逻辑
if (!hasPermission) {
return <div className="no-permission">您没有权限查看此页面</div>;
}
// 加载状态逻辑
if (loading) {
return <div className="loading">加载中...</div>;
}
// 核心业务逻辑
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>邮箱: {user.email}</p>
<p>年龄: {user.age}</p>
</div>
);
}
问题:
- 组件职责过多:权限、日志、性能、错误处理、业务逻辑全部混在一起
- 难以复用:其他组件也需要权限、加载、错误处理时,只能复制粘贴
- 难以测试:测试业务逻辑必须同时处理所有辅助逻辑
- 难以维护:修改加载逻辑可能影响权限检查
✅ 使用装饰器模式(HOC)解决
// UserProfile.jsx - 只关注核心业务逻辑
function UserProfile({ user }) {
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>邮箱: {user.email}</p>
<p>年龄: {user.age}</p>
</div>
);
}
export default UserProfile;
// decorators/withAuth.jsx - 权限装饰器
function withAuth(requiredPermission) {
return function(Component) {
return function WithAuthComponent(props) {
const [hasPermission, setHasPermission] = React.useState(null);
React.useEffect(() => {
checkUserPermission(requiredPermission).then(setHasPermission);
}, []);
if (hasPermission === null) {
return <div>检查权限中...</div>;
}
if (!hasPermission) {
return <div className="no-permission">您没有权限查看此页面</div>;
}
return <Component {...props} />;
};
};
}
export default withAuth;
// decorators/withLoading.jsx - 加载状态装饰器
function withLoading(fetchDataFn) {
return function(Component) {
return function WithLoadingComponent(props) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setLoading(true);
fetchDataFn(props)
.then(setData)
.finally(() => setLoading(false));
}, [props.userId]); // 根据实际情况调整依赖
if (loading) {
return <div className="loading">加载中...</div>;
}
return <Component {...props} data={data} />;
};
};
}
export default withLoading;
// decorators/withErrorBoundary.jsx - 错误边界装饰器
function withErrorBoundary(Component) {
return class WithErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('组件错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error">
<h2>出错了</h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return <Component {...this.props} />;
}
};
}
export default withErrorBoundary;
// decorators/withLogger.jsx - 日志装饰器
function withLogger(componentName) {
return function(Component) {
return function WithLoggerComponent(props) {
React.useEffect(() => {
console.log(`[${componentName}] 组件挂载`, props);
return () => {
console.log(`[${componentName}] 组件卸载`);
};
}, []);
React.useEffect(() => {
console.log(`[${componentName}] Props 更新`, props);
}, [props]);
return <Component {...props} />;
};
};
}
export default withLogger;
// decorators/withPerformance.jsx - 性能监控装饰器
function withPerformance(componentName) {
return function(Component) {
return function WithPerformanceComponent(props) {
const renderStartTime = React.useRef(performance.now());
React.useEffect(() => {
const renderEndTime = performance.now();
const renderTime = renderEndTime - renderStartTime.current;
console.log(`[性能] ${componentName} 渲染耗时: ${renderTime.toFixed(2)}ms`);
});
return <Component {...props} />;
};
};
}
export default withPerformance;
// UserProfileContainer.jsx - 组合使用装饰器
import UserProfile from './UserProfile';
import withAuth from './decorators/withAuth';
import withLoading from './decorators/withLoading';
import withErrorBoundary from './decorators/withErrorBoundary';
import withLogger from './decorators/withLogger';
import withPerformance from './decorators/withPerformance';
import { fetchUser } from './api';
// 组合多个装饰器
const EnhancedUserProfile = withErrorBoundary(
withAuth('user:read')(
withLoading(({ userId }) => fetchUser(userId))(
withLogger('UserProfile')(
withPerformance('UserProfile')(
UserProfile
)
)
)
)
);
// 使用
function App() {
return <EnhancedUserProfile userId={1} />;
}
效果:
- ✅ 单一职责:每个装饰器只做一件事
- ✅ 高度复用:装饰器可以用于任何组件
- ✅ 易于测试:可以独立测试业务组件和装饰器
- ✅ 灵活组合:可以自由组合不同的装饰器
🎯 使用 Hooks 实现装饰器模式
在现代 React 中,我们更倾向于使用 Hooks:
// hooks/useAuth.js
function useAuth(requiredPermission) {
const [hasPermission, setHasPermission] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
checkUserPermission(requiredPermission)
.then(setHasPermission)
.finally(() => setLoading(false));
}, [requiredPermission]);
return { hasPermission, loading };
}
export default useAuth;
// hooks/useDataFetching.js
function useDataFetching(fetchFn, deps = []) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
setLoading(true);
setError(null);
fetchFn()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, deps);
return { data, loading, error };
}
export default useDataFetching;
// hooks/useLogger.js
function useLogger(componentName, data) {
React.useEffect(() => {
console.log(`[${componentName}] 数据更新:`, data);
}, [componentName, data]);
}
export default useLogger;
// UserProfile.jsx - 使用 Hooks 组合功能
import useAuth from './hooks/useAuth';
import useDataFetching from './hooks/useDataFetching';
import useLogger from './hooks/useLogger';
import { fetchUser } from './api';
function UserProfile({ userId }) {
// 权限检查
const { hasPermission, loading: authLoading } = useAuth('user:read');
// 数据加载
const { data: user, loading: dataLoading, error } = useDataFetching(
() => fetchUser(userId),
[userId]
);
// 日志记录
useLogger('UserProfile', user);
if (authLoading) {
return <div>检查权限中...</div>;
}
if (!hasPermission) {
return <div>您没有权限查看此页面</div>;
}
if (error) {
return <div>错误: {error.message}</div>;
}
if (dataLoading) {
return <div>加载中...</div>;
}
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>邮箱: {user.email}</p>
<p>年龄: {user.age}</p>
</div>
);
}
🏗️ 真实业务场景
场景1:API 请求装饰器(中间件)
// api/middleware/authMiddleware.js - 认证中间件
function authMiddleware(request) {
return function decoratedRequest(config) {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return request(config);
};
}
// api/middleware/loggerMiddleware.js - 日志中间件
function loggerMiddleware(request) {
return function decoratedRequest(config) {
console.log('API 请求:', config.url, config);
const startTime = Date.now();
return request(config).then(
response => {
const duration = Date.now() - startTime;
console.log(`API 响应: ${config.url} (${duration}ms)`, response);
return response;
},
error => {
const duration = Date.now() - startTime;
console.error(`API 错误: ${config.url} (${duration}ms)`, error);
throw error;
}
);
};
}
// api/middleware/retryMiddleware.js - 重试中间件
function retryMiddleware(maxRetries = 3) {
return function(request) {
return function decoratedRequest(config) {
let retries = 0;
const attemptRequest = () => {
return request(config).catch(error => {
if (retries < maxRetries && isRetryableError(error)) {
retries++;
console.log(`重试请求 (${retries}/${maxRetries}):`, config.url);
return new Promise(resolve => setTimeout(resolve, 1000 * retries))
.then(attemptRequest);
}
throw error;
});
};
return attemptRequest();
};
};
}
function isRetryableError(error) {
return error.response?.status >= 500 || error.code === 'ECONNABORTED';
}
// api/middleware/cacheMiddleware.js - 缓存中间件
function cacheMiddleware(request) {
const cache = new Map();
return function decoratedRequest(config) {
if (config.method?.toLowerCase() !== 'get') {
return request(config);
}
const cacheKey = `${config.url}?${JSON.stringify(config.params)}`;
if (cache.has(cacheKey)) {
console.log('从缓存返回:', cacheKey);
return Promise.resolve(cache.get(cacheKey));
}
return request(config).then(response => {
cache.set(cacheKey, response);
return response;
});
};
}
// api/client.js - 组合中间件
function createApiClient() {
// 基础请求函数
const baseRequest = (config) => {
return fetch(config.url, {
method: config.method || 'GET',
headers: config.headers,
body: config.data ? JSON.stringify(config.data) : undefined,
}).then(response => response.json());
};
// 层层装饰
let request = baseRequest;
request = authMiddleware(request);
request = loggerMiddleware(request);
request = retryMiddleware(3)(request);
request = cacheMiddleware(request);
return {
get: (url, config = {}) => request({ ...config, url, method: 'GET' }),
post: (url, data, config = {}) => request({ ...config, url, method: 'POST', data }),
};
}
export default createApiClient();
// 使用
import apiClient from './api/client';
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
// 自动带上认证、日志、重试、缓存功能
apiClient.get('/users').then(setUsers);
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
场景2:表单验证装饰器
// decorators/withFormValidation.jsx
function withFormValidation(validationRules) {
return function(Component) {
return function WithValidationComponent(props) {
const [values, setValues] = React.useState({});
const [errors, setErrors] = React.useState({});
const [touched, setTouched] = React.useState({});
const validate = (fieldName, value) => {
const rules = validationRules[fieldName];
if (!rules) return '';
for (const rule of rules) {
const error = rule(value, values);
if (error) return error;
}
return '';
};
const handleChange = (fieldName, value) => {
setValues(prev => ({ ...prev, [fieldName]: value }));
if (touched[fieldName]) {
const error = validate(fieldName, value);
setErrors(prev => ({ ...prev, [fieldName]: error }));
}
};
const handleBlur = (fieldName) => {
setTouched(prev => ({ ...prev, [fieldName]: true }));
const error = validate(fieldName, values[fieldName]);
setErrors(prev => ({ ...prev, [fieldName]: error }));
};
const handleSubmit = (e) => {
e.preventDefault();
// 验证所有字段
const newErrors = {};
Object.keys(validationRules).forEach(fieldName => {
const error = validate(fieldName, values[fieldName]);
if (error) newErrors[fieldName] = error;
});
setErrors(newErrors);
setTouched(
Object.keys(validationRules).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {})
);
if (Object.keys(newErrors).length === 0) {
props.onSubmit?.(values);
}
};
return (
<Component
{...props}
values={values}
errors={errors}
touched={touched}
onChange={handleChange}
onBlur={handleBlur}
onSubmit={handleSubmit}
/>
);
};
};
}
// 验证规则
const required = (message = '此字段为必填') => (value) => {
return !value ? message : '';
};
const minLength = (length, message) => (value) => {
return value && value.length < length ? message || `最少${length}个字符` : '';
};
const email = (message = '请输入有效的邮箱') => (value) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return value && !regex.test(value) ? message : '';
};
export { withFormValidation, required, minLength, email };
// LoginForm.jsx
import { withFormValidation, required, minLength, email } from './decorators/withFormValidation';
function LoginForm({ values, errors, touched, onChange, onBlur, onSubmit }) {
return (
<form onSubmit={onSubmit}>
<div>
<input
type="email"
placeholder="邮箱"
value={values.email || ''}
onChange={(e) => onChange('email', e.target.value)}
onBlur={() => onBlur('email')}
/>
{touched.email && errors.email && (
<span className="error">{errors.email}</span>
)}
</div>
<div>
<input
type="password"
placeholder="密码"
value={values.password || ''}
onChange={(e) => onChange('password', e.target.value)}
onBlur={() => onBlur('password')}
/>
{touched.password && errors.password && (
<span className="error">{errors.password}</span>
)}
</div>
<button type="submit">登录</button>
</form>
);
}
// 装饰表单
const EnhancedLoginForm = withFormValidation({
email: [required('邮箱不能为空'), email()],
password: [required('密码不能为空'), minLength(6, '密码至少6个字符')],
})(LoginForm);
// 使用
function App() {
const handleSubmit = (values) => {
console.log('提交表单:', values);
// 调用登录 API
};
return <EnhancedLoginForm onSubmit={handleSubmit} />;
}
🎨 在主流框架中的应用
React - 高阶组件(HOC)
// React Router 的 withRouter
import { withRouter } from 'react-router-dom';
function MyComponent({ history, location, match }) {
return <div>当前路径: {location.pathname}</div>;
}
export default withRouter(MyComponent);
// Redux 的 connect
import { connect } from 'react-redux';
function UserProfile({ user, loadUser }) {
return <div>{user.name}</div>;
}
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = {
loadUser: loadUserAction,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
JavaScript Decorator 语法
// 使用 @decorator 语法(需要 Babel 支持)
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
function log(target, key, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用 ${key},参数:`, args);
const result = original.apply(this, args);
console.log(`${key} 返回:`, result);
return result;
};
return descriptor;
}
class User {
@readonly
name = 'John';
@log
sayHello(greeting) {
return `${greeting}, ${this.name}`;
}
}
const user = new User();
user.sayHello('Hi'); // 自动打印日志
// user.name = 'Jane'; // 报错,只读属性
Express 中间件 - 装饰器模式的典型应用
const express = require('express');
const app = express();
// 日志中间件
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
// 认证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
// 验证 token
next();
}
// 组合使用中间件
app.use(logger);
app.use('/api', authenticate); // 只对 /api 路由启用认证
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
⚖️ 优缺点分析
✅ 优点
- 单一职责:每个装饰器只做一件事
- 开闭原则:可以动态添加功能,无需修改原有代码
- 高度复用:装饰器可以应用于多个组件
- 灵活组合:可以自由组合不同的装饰器
- 易于测试:可以独立测试装饰器和核心组件
❌ 缺点
- 多层包装:过多的装饰器会导致层次过深
- 调试困难:错误堆栈可能很长,难以定位问题
- 顺序敏感:装饰器的顺序可能影响结果
- Props 传递:HOC 需要手动传递 props
📋 何时使用装饰器模式
✅ 适合使用的场景
- 需要动态添加功能(权限、日志、性能监控)
- 多个组件需要相同的功能增强
- 中间件系统(API 请求、Express 路由)
- 需要组合多个独立功能
- 不想修改原有代码
❌ 不适合使用的场景
- 只在一个地方使用的功能
- 装饰器之间有复杂的依赖关系
- 需要频繁修改核心逻辑
- 性能敏感的场景(装饰器有一定开销)
🎓 装饰器模式最佳实践
// ✅ 好的实践:装饰器职责单一、可组合
const withAuth = (permission) => (Component) => { /* ... */ };
const withLoading = (fetchFn) => (Component) => { /* ... */ };
const withErrorBoundary = (Component) => { /* ... */ };
// 组合使用
const Enhanced = withAuth('user:read')(
withLoading(fetchUser)(
withErrorBoundary(UserProfile)
)
);
// ❌ 坏的实践:一个装饰器做太多事
const withEverything = (Component) => {
// 认证、加载、错误处理、日志全部混在一起
// 难以复用和测试
};
// ✅ 使用 compose 函数优化组合
function compose(...fns) {
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
const enhance = compose(
withAuth('user:read'),
withLoading(fetchUser),
withErrorBoundary
);
const Enhanced = enhance(UserProfile);
代理模式
💡 模式定义
代理模式(Proxy Pattern)为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。
🤔 为什么需要代理模式?
问题场景:控制对象访问、懒加载、缓存、权限控制
❌ 不使用代理模式的痛点
// ImageGallery.jsx - 直接加载所有图片
function ImageGallery({ images }) {
return (
<div className="gallery">
{images.map(image => (
<div key={image.id} className="image-item">
{/* 所有图片立即加载,即使不在可视区域 */}
<img
src={image.largeUrl} // 加载大图
alt={image.title}
style={{ width: '100%', height: 'auto' }}
/>
<p>{image.title}</p>
</div>
))}
</div>
);
}
// 使用
function App() {
const images = [
{ id: 1, title: '图片1', largeUrl: 'large1.jpg' },
{ id: 2, title: '图片2', largeUrl: 'large2.jpg' },
// ... 100 张图片
];
return <ImageGallery images={images} />;
}
问题:
- 页面加载慢:所有图片立即加载,即使不在可视区域
- 浪费带宽:用户可能不会浏览到所有图片
- 内存占用高:100 张大图同时加载
✅ 使用代理模式解决(懒加载图片)
// components/LazyImage.jsx - 图片代理组件
function LazyImage({ src, thumbnail, alt, ...props }) {
const [imageSrc, setImageSrc] = React.useState(thumbnail || null);
const [isLoading, setIsLoading] = React.useState(true);
const imgRef = React.useRef();
React.useEffect(() => {
// 使用 Intersection Observer 监听图片是否进入视口
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 图片进入视口,开始加载真实图片
loadImage(src);
observer.unobserve(entry.target);
}
});
},
{
rootMargin: '50px', // 提前 50px 开始加载
}
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => {
if (imgRef.current) {
observer.unobserve(imgRef.current);
}
};
}, [src]);
const loadImage = (imageSrc) => {
const img = new Image();
img.src = imageSrc;
img.onload = () => {
setImageSrc(imageSrc);
setIsLoading(false);
};
img.onerror = () => {
setIsLoading(false);
console.error('图片加载失败:', imageSrc);
};
};
return (
<div ref={imgRef} className="lazy-image-container">
<img
src={imageSrc}
alt={alt}
className={isLoading ? 'loading' : 'loaded'}
{...props}
/>
{isLoading && <div className="placeholder">加载中...</div>}
</div>
);
}
export default LazyImage;
// ImageGallery.jsx - 使用代理组件
import LazyImage from './components/LazyImage';
function ImageGallery({ images }) {
return (
<div className="gallery">
{images.map(image => (
<div key={image.id} className="image-item">
{/* 使用代理组件,只在需要时加载 */}
<LazyImage
src={image.largeUrl}
thumbnail={image.thumbnailUrl}
alt={image.title}
style={{ width: '100%', height: 'auto' }}
/>
<p>{image.title}</p>
</div>
))}
</div>
);
}
效果:
- ✅ 按需加载:只加载可视区域的图片
- ✅ 节省带宽:用户不看的图片不加载
- ✅ 性能提升:页面加载更快
- ✅ 用户体验:先显示缩略图,再加载大图
🎯 ES6 Proxy 实现响应式数据
// reactive.js - 使用 Proxy 实现响应式
function reactive(target) {
const handlers = {
get(target, key, receiver) {
console.log(`读取属性: ${key}`);
// 如果属性值是对象,递归代理
const result = Reflect.get(target, key, receiver);
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
console.log(`设置属性: ${key}, 旧值: ${oldValue}, 新值: ${value}`);
const result = Reflect.set(target, key, value, receiver);
// 触发更新
if (oldValue !== value) {
console.log(`属性 ${key} 发生变化,触发更新`);
}
return result;
},
deleteProperty(target, key) {
console.log(`删除属性: ${key}`);
return Reflect.deleteProperty(target, key);
},
};
return new Proxy(target, handlers);
}
// 使用
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing',
street: 'xxx'
}
});
user.name; // 读取属性: name
user.name = 'Jane'; // 设置属性: name, 旧值: John, 新值: Jane
user.address.city; // 读取属性: address -> 读取属性: city
user.address.city = 'Shanghai'; // 触发更新
🏗️ 真实业务场景
场景1:API 缓存代理
// api/cacheProxy.js - API 缓存代理
class ApiCacheProxy {
constructor(apiClient, options = {}) {
this.apiClient = apiClient;
this.cache = new Map();
this.cacheTime = options.cacheTime || 5 * 60 * 1000; // 默认 5 分钟
this.maxCacheSize = options.maxCacheSize || 100; // 最多缓存 100 个请求
}
_getCacheKey(url, params) {
return `${url}?${JSON.stringify(params || {})}`;
}
_isExpired(cacheEntry) {
return Date.now() - cacheEntry.timestamp > this.cacheTime;
}
_cleanupCache() {
if (this.cache.size >= this.maxCacheSize) {
// 删除最旧的缓存
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
async get(url, params, options = {}) {
const cacheKey = this._getCacheKey(url, params);
// 检查缓存
if (this.cache.has(cacheKey) && !options.forceRefresh) {
const cacheEntry = this.cache.get(cacheKey);
if (!this._isExpired(cacheEntry)) {
console.log('从缓存返回:', url);
return cacheEntry.data;
} else {
// 缓存过期,删除
this.cache.delete(cacheKey);
}
}
// 发起真实请求
console.log('从 API 获取:', url);
const data = await this.apiClient.get(url, params);
// 存入缓存
this._cleanupCache();
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
return data;
}
async post(url, data, params) {
// POST 请求会使缓存失效
this.clearCache();
return await this.apiClient.post(url, data, params);
}
clearCache() {
console.log('清空缓存');
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size,
maxSize: this.maxCacheSize,
entries: Array.from(this.cache.entries()).map(([key, value]) => ({
key,
timestamp: value.timestamp,
age: Date.now() - value.timestamp,
})),
};
}
}
export default ApiCacheProxy;
// 使用缓存代理
import axios from 'axios';
import ApiCacheProxy from './api/cacheProxy';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
const cachedApi = new ApiCacheProxy(apiClient, {
cacheTime: 10 * 60 * 1000, // 10 分钟
maxCacheSize: 50,
});
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
// 第一次从 API 获取
cachedApi.get('/users').then(setUsers);
// 5 秒后再次请求,会从缓存返回
setTimeout(() => {
cachedApi.get('/users').then(data => {
console.log('第二次请求,从缓存获取');
});
}, 5000);
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
场景2:权限控制代理
// security/PermissionProxy.js - 权限控制代理
class PermissionProxy {
constructor(targetObject, userPermissions) {
this.target = targetObject;
this.permissions = userPermissions;
return new Proxy(targetObject, {
get: (target, prop) => {
// 检查方法权限
const requiredPermission = target.getRequiredPermission?.(prop);
if (requiredPermission && !this.hasPermission(requiredPermission)) {
throw new Error(`没有权限执行操作: ${prop}`);
}
const value = target[prop];
// 如果是函数,返回代理后的函数
if (typeof value === 'function') {
return (...args) => {
console.log(`执行操作: ${prop}`);
return value.apply(target, args);
};
}
return value;
},
set: (target, prop, value) => {
// 检查写入权限
if (!this.hasPermission('write')) {
throw new Error('没有写入权限');
}
console.log(`设置属性: ${prop} = ${value}`);
target[prop] = value;
return true;
},
});
}
hasPermission(permission) {
return this.permissions.includes(permission);
}
}
// 使用
class UserManager {
constructor() {
this.users = [];
}
getRequiredPermission(method) {
const permissionMap = {
getUsers: 'user:read',
addUser: 'user:create',
deleteUser: 'user:delete',
};
return permissionMap[method];
}
getUsers() {
return this.users;
}
addUser(user) {
this.users.push(user);
return user;
}
deleteUser(userId) {
this.users = this.users.filter(u => u.id !== userId);
}
}
// 创建代理
const adminManager = new PermissionProxy(
new UserManager(),
['user:read', 'user:create', 'user:delete'] // 管理员权限
);
const guestManager = new PermissionProxy(
new UserManager(),
['user:read'] // 游客只有读权限
);
// 管理员可以执行所有操作
adminManager.addUser({ id: 1, name: 'John' }); // ✅ 成功
adminManager.getUsers(); // ✅ 成功
// 游客只能读取
guestManager.getUsers(); // ✅ 成功
// guestManager.addUser({ id: 2, name: 'Jane' }); // ❌ 抛出错误:没有权限
场景3:虚拟代理(懒加载模块)
// modules/lazyModuleProxy.js - 模块懒加载代理
function createLazyModuleProxy(importFn) {
let moduleCache = null;
let loadingPromise = null;
return new Proxy({}, {
get(target, prop) {
// 如果模块已加载,直接返回
if (moduleCache) {
return moduleCache[prop];
}
// 如果正在加载,等待加载完成
if (loadingPromise) {
return async (...args) => {
const module = await loadingPromise;
return module[prop](...args);
};
}
// 开始加载模块
loadingPromise = importFn().then(module => {
moduleCache = module;
loadingPromise = null;
return module;
});
// 返回一个异步函数
return async (...args) => {
const module = await loadingPromise;
return module[prop](...args);
};
},
});
}
// 使用
const heavyModule = createLazyModuleProxy(() => import('./heavyModule'));
// 第一次调用时才加载模块
async function handleClick() {
await heavyModule.doSomething(); // 触发模块加载
}
🎨 在主流框架中的应用
Vue 3 响应式系统 - Proxy 的典型应用
// Vue 3 使用 Proxy 实现响应式
import { reactive, effect } from 'vue';
const state = reactive({
count: 0,
user: {
name: 'John',
},
});
// 自动追踪依赖
effect(() => {
console.log('count:', state.count);
});
state.count++; // 自动触发 effect,打印 "count: 1"
Mobx - Proxy 实现状态管理
import { observable, autorun } from 'mobx';
const store = observable({
count: 0,
increment() {
this.count++;
},
});
autorun(() => {
console.log('Count:', store.count);
});
store.increment(); // 自动触发 autorun
Immer - Proxy 实现不可变数据
import produce from 'immer';
const state = {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
],
};
const nextState = produce(state, draft => {
// 可以直接修改 draft,Immer 会自动创建新对象
draft.users[0].name = 'John Doe';
draft.users.push({ id: 3, name: 'Bob' });
});
console.log(state === nextState); // false,创建了新对象
console.log(state.users === nextState.users); // false
⚖️ 优缺点分析
✅ 优点
- 控制访问:可以在访问对象前进行权限检查
- 懒加载:延迟创建开销大的对象
- 缓存:缓存结果,提升性能
- 日志记录:记录对象的访问和修改
- 远程代理:隐藏对象在不同地址空间的事实
❌ 缺点
- 增加复杂度:引入额外的代理层
- 性能开销:每次访问都要经过代理
- 调试困难:错误堆栈可能不直观
📋 何时使用代理模式
✅ 适合使用的场景
- 懒加载(虚拟代理):延迟创建开销大的对象
- 权限控制(保护代理):控制对对象的访问
- 缓存(智能代理):缓存结果,避免重复计算
- 远程代理:访问远程对象
- 日志记录:记录对象访问
❌ 不适合使用的场景
- 简单对象,不需要额外控制
- 性能敏感的场景
- 代理逻辑比目标对象还复杂
适配器模式
💡 模式定义
适配器模式(Adapter Pattern)将一个类的接口转换成客户端期望的另一个接口,使得原本接口不兼容的类可以一起工作。
🤔 为什么需要适配器模式?
问题场景:接口不兼容、第三方库迁移、数据格式转换
假设你的项目最初使用 Axios,现在要迁移到 Fetch API:
❌ 不使用适配器模式的痛点
// 原来的代码使用 Axios
import axios from 'axios';
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
axios.get('/api/users')
.then(response => setUsers(response.data))
.catch(error => console.error(error));
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
function ProductList() {
const [products, setProducts] = React.useState([]);
React.useEffect(() => {
axios.get('/api/products')
.then(response => setProducts(response.data))
.catch(error => console.error(error));
}, []);
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// ... 100 个组件都使用了 axios.get
现在需要迁移到 Fetch:
// 需要逐个修改所有组件 😱
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
fetch('/api/users')
.then(response => response.json()) // 不同!
.then(data => setUsers(data)) // 不同!
.catch(error => console.error(error));
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// 需要修改所有 100 个组件... 😭
问题:
- 修改成本高:需要修改所有使用 Axios 的地方
- 容易出错:可能漏掉某些地方
- API 不一致:Axios 和 Fetch 的返回值结构不同
✅ 使用适配器模式解决
// api/httpAdapter.js - HTTP 客户端适配器
class HttpAdapter {
constructor(client) {
this.client = client;
}
async get(url, config = {}) {
return this.request({ ...config, url, method: 'GET' });
}
async post(url, data, config = {}) {
return this.request({ ...config, url, method: 'POST', data });
}
async put(url, data, config = {}) {
return this.request({ ...config, url, method: 'PUT', data });
}
async delete(url, config = {}) {
return this.request({ ...config, url, method: 'DELETE' });
}
async request(config) {
throw new Error('Subclass must implement request method');
}
}
// api/axiosAdapter.js - Axios 适配器
import axios from 'axios';
class AxiosAdapter extends HttpAdapter {
async request(config) {
try {
const response = await axios({
url: config.url,
method: config.method,
data: config.data,
params: config.params,
headers: config.headers,
});
// 统一返回格式
return {
data: response.data,
status: response.status,
headers: response.headers,
};
} catch (error) {
throw {
message: error.message,
status: error.response?.status,
data: error.response?.data,
};
}
}
}
// api/fetchAdapter.js - Fetch 适配器
class FetchAdapter extends HttpAdapter {
async request(config) {
try {
const response = await fetch(config.url, {
method: config.method,
headers: {
'Content-Type': 'application/json',
...config.headers,
},
body: config.data ? JSON.stringify(config.data) : undefined,
});
const data = await response.json();
if (!response.ok) {
throw {
message: `HTTP ${response.status}`,
status: response.status,
data,
};
}
// 统一返回格式(与 Axios 适配器相同)
return {
data,
status: response.status,
headers: response.headers,
};
} catch (error) {
throw {
message: error.message,
status: error.status,
data: error.data,
};
}
}
}
export { HttpAdapter, AxiosAdapter, FetchAdapter };
// api/client.js - 使用适配器
import { AxiosAdapter, FetchAdapter } from './httpAdapter';
// 可以轻松切换实现
// const httpClient = new AxiosAdapter();
const httpClient = new FetchAdapter();
export default httpClient;
// 组件代码无需修改!
import httpClient from './api/client';
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
// 无论底层是 Axios 还是 Fetch,代码都不用改
httpClient.get('/api/users')
.then(response => setUsers(response.data))
.catch(error => console.error(error));
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
效果:
- ✅ 统一接口:所有 HTTP 客户端使用相同接口
- ✅ 易于切换:修改一行代码即可切换实现
- ✅ 零改动:组件代码无需修改
- ✅ 易于扩展:可以轻松添加新的适配器
🏗️ 真实业务场景
场景1:LocalStorage 适配器(兼容不同存储方案)
// storage/StorageAdapter.js
class StorageAdapter {
get(key) {
throw new Error('Subclass must implement get method');
}
set(key, value) {
throw new Error('Subclass must implement set method');
}
remove(key) {
throw new Error('Subclass must implement remove method');
}
clear() {
throw new Error('Subclass must implement clear method');
}
}
// storage/LocalStorageAdapter.js
class LocalStorageAdapter extends StorageAdapter {
get(key) {
try {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error('LocalStorage get error:', error);
return null;
}
}
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('LocalStorage set error:', error);
return false;
}
}
remove(key) {
localStorage.removeItem(key);
}
clear() {
localStorage.clear();
}
}
// storage/SessionStorageAdapter.js
class SessionStorageAdapter extends StorageAdapter {
get(key) {
try {
const value = sessionStorage.getItem(key);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error('SessionStorage get error:', error);
return null;
}
}
set(key, value) {
try {
sessionStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('SessionStorage set error:', error);
return false;
}
}
remove(key) {
sessionStorage.removeItem(key);
}
clear() {
sessionStorage.clear();
}
}
// storage/MemoryStorageAdapter.js - 内存存储(用于测试或不支持 localStorage 的环境)
class MemoryStorageAdapter extends StorageAdapter {
constructor() {
super();
this.store = new Map();
}
get(key) {
return this.store.get(key) || null;
}
set(key, value) {
this.store.set(key, value);
return true;
}
remove(key) {
this.store.delete(key);
}
clear() {
this.store.clear();
}
}
// storage/CookieStorageAdapter.js - Cookie 存储适配器
class CookieStorageAdapter extends StorageAdapter {
get(key) {
const name = key + '=';
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.indexOf(name) === 0) {
const value = cookie.substring(name.length);
try {
return JSON.parse(value);
} catch {
return value;
}
}
}
return null;
}
set(key, value, days = 365) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = 'expires=' + date.toUTCString();
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
document.cookie = `${key}=${stringValue};${expires};path=/`;
return true;
}
remove(key) {
document.cookie = `${key}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/`;
}
clear() {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const key = cookie.split('=')[0].trim();
this.remove(key);
}
}
}
// storage/index.js - 统一导出
function createStorage(type = 'local') {
switch (type) {
case 'local':
return new LocalStorageAdapter();
case 'session':
return new SessionStorageAdapter();
case 'memory':
return new MemoryStorageAdapter();
case 'cookie':
return new CookieStorageAdapter();
default:
return new LocalStorageAdapter();
}
}
export default createStorage;
// 使用存储适配器
import createStorage from './storage';
const storage = createStorage('local'); // 可以轻松切换为 'session', 'memory', 'cookie'
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
// 从存储加载用户
const savedUser = storage.get('user');
if (savedUser) {
setUser(savedUser);
}
}, []);
const handleLogin = (userData) => {
setUser(userData);
storage.set('user', userData); // 保存到存储
};
const handleLogout = () => {
setUser(null);
storage.remove('user'); // 从存储删除
};
return (
<div>
{user ? (
<div>
<p>欢迎, {user.name}</p>
<button onClick={handleLogout}>退出</button>
</div>
) : (
<button onClick={() => handleLogin({ name: 'John' })}>登录</button>
)}
</div>
);
}
场景2:日期库适配器(兼容 Moment.js、Day.js、date-fns)
// utils/dateAdapter.js
class DateAdapter {
format(date, formatStr) {
throw new Error('Subclass must implement format method');
}
parse(dateStr, formatStr) {
throw new Error('Subclass must implement parse method');
}
add(date, amount, unit) {
throw new Error('Subclass must implement add method');
}
diff(date1, date2, unit) {
throw new Error('Subclass must implement diff method');
}
isBefore(date1, date2) {
throw new Error('Subclass must implement isBefore method');
}
isAfter(date1, date2) {
throw new Error('Subclass must implement isAfter method');
}
}
// utils/MomentAdapter.js
import moment from 'moment';
class MomentAdapter extends DateAdapter {
format(date, formatStr = 'YYYY-MM-DD') {
return moment(date).format(formatStr);
}
parse(dateStr, formatStr = 'YYYY-MM-DD') {
return moment(dateStr, formatStr).toDate();
}
add(date, amount, unit) {
return moment(date).add(amount, unit).toDate();
}
diff(date1, date2, unit = 'days') {
return moment(date1).diff(moment(date2), unit);
}
isBefore(date1, date2) {
return moment(date1).isBefore(moment(date2));
}
isAfter(date1, date2) {
return moment(date1).isAfter(moment(date2));
}
}
// utils/DayJsAdapter.js
import dayjs from 'dayjs';
class DayJsAdapter extends DateAdapter {
format(date, formatStr = 'YYYY-MM-DD') {
return dayjs(date).format(formatStr);
}
parse(dateStr, formatStr = 'YYYY-MM-DD') {
return dayjs(dateStr, formatStr).toDate();
}
add(date, amount, unit) {
return dayjs(date).add(amount, unit).toDate();
}
diff(date1, date2, unit = 'day') {
return dayjs(date1).diff(dayjs(date2), unit);
}
isBefore(date1, date2) {
return dayjs(date1).isBefore(dayjs(date2));
}
isAfter(date1, date2) {
return dayjs(date1).isAfter(dayjs(date2));
}
}
// utils/NativeDateAdapter.js - 使用原生 Date 对象
class NativeDateAdapter extends DateAdapter {
format(date, formatStr = 'YYYY-MM-DD') {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return formatStr
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day);
}
parse(dateStr) {
return new Date(dateStr);
}
add(date, amount, unit) {
const d = new Date(date);
if (unit === 'days') {
d.setDate(d.getDate() + amount);
} else if (unit === 'months') {
d.setMonth(d.getMonth() + amount);
} else if (unit === 'years') {
d.setFullYear(d.getFullYear() + amount);
}
return d;
}
diff(date1, date2, unit = 'days') {
const diffMs = new Date(date1) - new Date(date2);
if (unit === 'days') {
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
}
return diffMs;
}
isBefore(date1, date2) {
return new Date(date1) < new Date(date2);
}
isAfter(date1, date2) {
return new Date(date1) > new Date(date2);
}
}
// 导出
export { DateAdapter, MomentAdapter, DayJsAdapter, NativeDateAdapter };
// 使用日期适配器
import { DayJsAdapter } from './utils/dateAdapter';
const dateUtil = new DayJsAdapter(); // 可切换为 MomentAdapter 或 NativeDateAdapter
function DateRangePicker() {
const [startDate, setStartDate] = React.useState(new Date());
const [endDate, setEndDate] = React.useState(
dateUtil.add(new Date(), 7, 'days') // 7 天后
);
const daysDiff = dateUtil.diff(endDate, startDate, 'days');
return (
<div>
<p>开始日期: {dateUtil.format(startDate)}</p>
<p>结束日期: {dateUtil.format(endDate)}</p>
<p>相差天数: {daysDiff} 天</p>
</div>
);
}
场景3:API 数据适配器(后端数据格式转换)
// adapters/userAdapter.js - 用户数据适配器
class UserAdapter {
// 将后端数据转换为前端格式
static toClient(serverUser) {
return {
id: serverUser.user_id,
name: serverUser.user_name,
email: serverUser.email_address,
avatar: serverUser.avatar_url,
createdAt: new Date(serverUser.created_at),
isActive: serverUser.status === 1,
role: this._mapRole(serverUser.role_id),
};
}
// 将前端数据转换为后端格式
static toServer(clientUser) {
return {
user_id: clientUser.id,
user_name: clientUser.name,
email_address: clientUser.email,
avatar_url: clientUser.avatar,
created_at: clientUser.createdAt?.toISOString(),
status: clientUser.isActive ? 1 : 0,
role_id: this._mapRoleToId(clientUser.role),
};
}
static toClientList(serverUsers) {
return serverUsers.map(user => this.toClient(user));
}
static _mapRole(roleId) {
const roleMap = {
1: 'admin',
2: 'user',
3: 'guest',
};
return roleMap[roleId] || 'guest';
}
static _mapRoleToId(role) {
const roleMap = {
admin: 1,
user: 2,
guest: 3,
};
return roleMap[role] || 3;
}
}
export default UserAdapter;
// 使用数据适配器
import UserAdapter from './adapters/userAdapter';
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
// 后端返回的数据格式
// { user_id: 1, user_name: 'John', email_address: 'john@example.com', ... }
// 使用适配器转换为前端格式
const clientUsers = UserAdapter.toClientList(data);
setUsers(clientUsers);
// 现在可以使用前端格式
// { id: 1, name: 'John', email: 'john@example.com', ... }
});
}, []);
const handleUpdateUser = (user) => {
// 将前端格式转换为后端格式
const serverUser = UserAdapter.toServer(user);
fetch(`/api/users/${user.id}`, {
method: 'PUT',
body: JSON.stringify(serverUser),
});
};
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} ({user.email}) - {user.role}
</li>
))}
</ul>
);
}
🎨 在主流框架中的应用
Axios 适配器系统
// Axios 内部使用适配器模式支持浏览器和 Node.js
import axios from 'axios';
// Axios 自动选择适配器
// 浏览器环境使用 XMLHttpRequest
// Node.js 环境使用 http/https 模块
// 也可以自定义适配器
const customAdapter = (config) => {
return new Promise((resolve, reject) => {
// 自定义请求逻辑
// ...
resolve({
data: {},
status: 200,
headers: {},
config,
});
});
};
const instance = axios.create({
adapter: customAdapter,
});
React 事件系统适配器
// React 内部使用适配器统一不同浏览器的事件
// 开发者使用统一的接口
function Button() {
const handleClick = (e) => {
// e 是 React 的合成事件,统一了所有浏览器的差异
console.log(e.target);
};
return <button onClick={handleClick}>Click</button>;
}
⚖️ 优缺点分析
✅ 优点
- 解耦:客户端与具体实现解耦
- 易于切换:可以轻松切换不同的实现
- 复用性:适配器可以复用于多个场景
- 符合开闭原则:添加新适配器不影响现有代码
❌ 缺点
- 增加复杂度:引入额外的适配器层
- 性能开销:数据转换有一定开销
- 过度使用:简单场景不需要适配器
📋 何时使用适配器模式
✅ 适合使用的场景
- 第三方库迁移(Axios → Fetch)
- 多种存储方案(localStorage、sessionStorage、Cookie)
- API 数据格式转换(后端 snake_case → 前端 camelCase)
- 跨平台适配(Web、React Native、小程序)
- 旧代码重构但不想修改接口
❌ 不适合使用的场景
- 接口已经统一
- 只有一种实现
- 数据格式简单,不需要转换
外观模式
💡 模式定义
外观模式(Facade Pattern)为复杂的子系统提供一个简单的统一接口,隐藏子系统的复杂性,使子系统更容易使用。
🤔 为什么需要外观模式?
问题场景:复杂的 API 调用、多个步骤的操作
假设你的应用需要初始化很多服务:
❌ 不使用外观模式的痛点
// App.jsx - 初始化逻辑散落各处
import { initAuth } from './services/auth';
import { initAnalytics } from './services/analytics';
import { initNotifications } from './services/notifications';
import { initDatabase } from './services/database';
import { initLogger } from './services/logger';
import { loadUserPreferences } from './services/preferences';
import { connectWebSocket } from './services/websocket';
function App() {
const [isInitialized, setIsInitialized] = React.useState(false);
React.useEffect(() => {
const initialize = async () => {
try {
// 初始化日志
await initLogger({
level: 'info',
endpoint: '/api/logs',
});
// 初始化数据库
await initDatabase({
name: 'myapp',
version: 1,
});
// 初始化认证
const authResult = await initAuth({
clientId: 'xxx',
redirectUri: window.location.origin,
});
if (!authResult.isAuthenticated) {
throw new Error('Authentication failed');
}
// 初始化分析
await initAnalytics({
trackingId: 'GA-XXXXX',
userId: authResult.user.id,
});
// 加载用户偏好
const preferences = await loadUserPreferences(authResult.user.id);
applyPreferences(preferences);
// 初始化通知
await initNotifications({
permission: true,
userId: authResult.user.id,
});
// 连接 WebSocket
await connectWebSocket({
url: 'wss://api.example.com',
token: authResult.token,
});
setIsInitialized(true);
} catch (error) {
console.error('初始化失败:', error);
// 清理已初始化的服务
// ...
}
};
initialize();
}, []);
if (!isInitialized) {
return <div>初始化中...</div>;
}
return <div>应用内容</div>;
}
问题:
- 初始化逻辑复杂,难以维护
- 错误处理困难
- 每个新项目都要复制粘贴这些代码
- 调用顺序容易出错
✅ 使用外观模式解决
// services/AppFacade.js - 应用外观
class AppFacade {
constructor(config = {}) {
this.config = {
logLevel: 'info',
analyticsId: null,
authConfig: {},
...config,
};
this.services = {
logger: null,
database: null,
auth: null,
analytics: null,
notifications: null,
websocket: null,
};
this.isInitialized = false;
}
// 统一初始化入口
async initialize() {
if (this.isInitialized) {
console.warn('App already initialized');
return;
}
try {
// 按顺序初始化所有服务
await this._initLogger();
await this._initDatabase();
const user = await this._initAuth();
await this._initAnalytics(user);
await this._loadPreferences(user);
await this._initNotifications(user);
await this._connectWebSocket(user);
this.isInitialized = true;
console.log('App initialized successfully');
} catch (error) {
console.error('App initialization failed:', error);
await this.cleanup();
throw error;
}
}
// 内部初始化方法
async _initLogger() {
const { initLogger } = await import('./logger');
this.services.logger = await initLogger({
level: this.config.logLevel,
endpoint: '/api/logs',
});
}
async _initDatabase() {
const { initDatabase } = await import('./database');
this.services.database = await initDatabase({
name: 'myapp',
version: 1,
});
}
async _initAuth() {
const { initAuth } = await import('./auth');
this.services.auth = await initAuth(this.config.authConfig);
if (!this.services.auth.isAuthenticated) {
throw new Error('Authentication failed');
}
return this.services.auth.user;
}
async _initAnalytics(user) {
if (!this.config.analyticsId) return;
const { initAnalytics } = await import('./analytics');
this.services.analytics = await initAnalytics({
trackingId: this.config.analyticsId,
userId: user.id,
});
}
async _loadPreferences(user) {
const { loadUserPreferences, applyPreferences } = await import('./preferences');
const preferences = await loadUserPreferences(user.id);
applyPreferences(preferences);
}
async _initNotifications(user) {
const { initNotifications } = await import('./notifications');
this.services.notifications = await initNotifications({
permission: true,
userId: user.id,
});
}
async _connectWebSocket(user) {
const { connectWebSocket } = await import('./websocket');
this.services.websocket = await connectWebSocket({
url: 'wss://api.example.com',
token: this.services.auth.token,
});
}
// 统一清理
async cleanup() {
console.log('Cleaning up services...');
if (this.services.websocket) {
this.services.websocket.disconnect();
}
if (this.services.notifications) {
this.services.notifications.disable();
}
// 清理其他服务...
this.isInitialized = false;
}
// 便捷方法
getService(name) {
return this.services[name];
}
isReady() {
return this.isInitialized;
}
}
export default AppFacade;
// App.jsx - 简洁的使用方式
import AppFacade from './services/AppFacade';
function App() {
const [isReady, setIsReady] = React.useState(false);
const appFacadeRef = React.useRef(null);
React.useEffect(() => {
const appFacade = new AppFacade({
logLevel: 'info',
analyticsId: 'GA-XXXXX',
authConfig: {
clientId: 'xxx',
redirectUri: window.location.origin,
},
});
appFacadeRef.current = appFacade;
// 一行代码完成所有初始化!
appFacade.initialize()
.then(() => setIsReady(true))
.catch(error => console.error('初始化失败:', error));
// 清理
return () => {
appFacade.cleanup();
};
}, []);
if (!isReady) {
return <div>初始化中...</div>;
}
return <div>应用内容</div>;
}
效果:
- ✅ 简化调用:一行代码完成复杂初始化
- ✅ 易于维护:所有初始化逻辑集中管理
- ✅ 错误处理:统一的错误处理和清理
- ✅ 可复用:可以在多个项目中复用
🏗️ 真实业务场景
场景1:文件上传外观
// services/UploadFacade.js - 文件上传外观
class UploadFacade {
constructor(config = {}) {
this.config = {
maxSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
endpoint: '/api/upload',
chunkSize: 1024 * 1024, // 1MB chunks
...config,
};
}
// 简单上传接口
async upload(file, options = {}) {
try {
// 1. 验证文件
this._validateFile(file);
// 2. 压缩图片(如果是图片)
const processedFile = await this._processFile(file);
// 3. 生成缩略图(如果是图片)
const thumbnail = await this._generateThumbnail(processedFile);
// 4. 上传文件
const uploadResult = await this._uploadFile(processedFile, options);
// 5. 上传缩略图
if (thumbnail) {
await this._uploadThumbnail(thumbnail, uploadResult.id);
}
// 6. 返回结果
return {
id: uploadResult.id,
url: uploadResult.url,
thumbnailUrl: uploadResult.thumbnailUrl,
size: processedFile.size,
type: processedFile.type,
};
} catch (error) {
console.error('上传失败:', error);
throw error;
}
}
// 分片上传
async uploadLargeFile(file, onProgress) {
this._validateFile(file);
const totalChunks = Math.ceil(file.size / this.config.chunkSize);
let uploadedChunks = 0;
// 创建上传任务
const uploadId = await this._createUploadTask(file);
// 分片上传
for (let i = 0; i < totalChunks; i++) {
const start = i * this.config.chunkSize;
const end = Math.min(start + this.config.chunkSize, file.size);
const chunk = file.slice(start, end);
await this._uploadChunk(uploadId, i, chunk);
uploadedChunks++;
onProgress?.((uploadedChunks / totalChunks) * 100);
}
// 合并分片
const result = await this._mergeChunks(uploadId);
return result;
}
// 批量上传
async uploadMultiple(files, onProgress) {
const results = [];
let completed = 0;
for (const file of files) {
try {
const result = await this.upload(file);
results.push({ success: true, file, result });
} catch (error) {
results.push({ success: false, file, error });
}
completed++;
onProgress?.((completed / files.length) * 100);
}
return results;
}
// 内部方法
_validateFile(file) {
if (file.size > this.config.maxSize) {
throw new Error(`文件大小超过限制 (${this.config.maxSize / 1024 / 1024}MB)`);
}
if (!this.config.allowedTypes.includes(file.type)) {
throw new Error(`不支持的文件类型: ${file.type}`);
}
}
async _processFile(file) {
if (!file.type.startsWith('image/')) {
return file;
}
// 压缩图片
return await this._compressImage(file);
}
async _compressImage(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算压缩后的尺寸
let width = img.width;
let height = img.height;
const maxDimension = 1920;
if (width > maxDimension || height > maxDimension) {
if (width > height) {
height = (height / width) * maxDimension;
width = maxDimension;
} else {
width = (width / height) * maxDimension;
height = maxDimension;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
resolve(new File([blob], file.name, { type: file.type }));
}, file.type, 0.9);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
async _generateThumbnail(file) {
if (!file.type.startsWith('image/')) {
return null;
}
// 生成缩略图逻辑
return null; // 简化实现
}
async _uploadFile(file, options) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(this.config.endpoint, {
method: 'POST',
body: formData,
headers: options.headers || {},
});
return await response.json();
}
async _uploadThumbnail(thumbnail, fileId) {
// 上传缩略图
}
async _createUploadTask(file) {
// 创建分片上传任务
return 'upload-id-123';
}
async _uploadChunk(uploadId, chunkIndex, chunk) {
// 上传单个分片
}
async _mergeChunks(uploadId) {
// 合并所有分片
}
}
export default UploadFacade;
// 使用上传外观
import UploadFacade from './services/UploadFacade';
function FileUploader() {
const [uploading, setUploading] = React.useState(false);
const [progress, setProgress] = React.useState(0);
const uploadFacade = new UploadFacade({
maxSize: 20 * 1024 * 1024, // 20MB
allowedTypes: ['image/jpeg', 'image/png'],
});
const handleFileSelect = async (e) => {
const file = e.target.files[0];
if (!file) return;
setUploading(true);
try {
// 简单调用,内部自动处理压缩、缩略图、上传等
const result = await uploadFacade.upload(file);
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
} finally {
setUploading(false);
}
};
const handleLargeFileSelect = async (e) => {
const file = e.target.files[0];
if (!file) return;
setUploading(true);
try {
// 大文件自动分片上传
const result = await uploadFacade.uploadLargeFile(file, setProgress);
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
} finally {
setUploading(false);
setProgress(0);
}
};
return (
<div>
<input type="file" onChange={handleFileSelect} disabled={uploading} />
<input type="file" onChange={handleLargeFileSelect} disabled={uploading} />
{uploading && <div>上传中... {progress.toFixed(0)}%</div>}
</div>
);
}
场景2:支付外观
// services/PaymentFacade.js - 支付外观
class PaymentFacade {
constructor() {
this.providers = {
alipay: null,
wechat: null,
stripe: null,
};
}
// 统一支付接口
async pay(provider, amount, options = {}) {
try {
// 1. 验证金额
this._validateAmount(amount);
// 2. 创建订单
const order = await this._createOrder(amount, options);
// 3. 调用支付提供商
let paymentResult;
switch (provider) {
case 'alipay':
paymentResult = await this._payWithAlipay(order);
break;
case 'wechat':
paymentResult = await this._payWithWechat(order);
break;
case 'stripe':
paymentResult = await this._payWithStripe(order);
break;
default:
throw new Error(`不支持的支付方式: ${provider}`);
}
// 4. 记录支付日志
await this._logPayment(order, paymentResult);
// 5. 发送通知
await this._sendNotification(order, paymentResult);
return {
success: true,
orderId: order.id,
transactionId: paymentResult.transactionId,
};
} catch (error) {
console.error('支付失败:', error);
throw error;
}
}
// 查询支付状态
async queryStatus(orderId) {
const order = await this._getOrder(orderId);
return {
orderId: order.id,
status: order.status,
amount: order.amount,
createdAt: order.createdAt,
};
}
// 退款
async refund(orderId, amount) {
const order = await this._getOrder(orderId);
// 调用对应支付提供商的退款接口
let refundResult;
switch (order.provider) {
case 'alipay':
refundResult = await this._refundAlipay(order, amount);
break;
case 'wechat':
refundResult = await this._refundWechat(order, amount);
break;
case 'stripe':
refundResult = await this._refundStripe(order, amount);
break;
}
await this._logRefund(order, refundResult);
return refundResult;
}
// 内部方法
_validateAmount(amount) {
if (amount <= 0) {
throw new Error('金额必须大于0');
}
}
async _createOrder(amount, options) {
return {
id: 'ORDER-' + Date.now(),
amount,
status: 'pending',
createdAt: new Date(),
...options,
};
}
async _payWithAlipay(order) {
// 调用支付宝 SDK
return { transactionId: 'ALIPAY-' + Date.now() };
}
async _payWithWechat(order) {
// 调用微信支付 SDK
return { transactionId: 'WECHAT-' + Date.now() };
}
async _payWithStripe(order) {
// 调用 Stripe SDK
return { transactionId: 'STRIPE-' + Date.now() };
}
async _logPayment(order, result) {
console.log('支付日志:', order, result);
}
async _sendNotification(order, result) {
console.log('发送通知:', order, result);
}
async _getOrder(orderId) {
// 获取订单信息
return { id: orderId, provider: 'alipay' };
}
async _refundAlipay(order, amount) {
// 支付宝退款
}
async _refundWechat(order, amount) {
// 微信退款
}
async _refundStripe(order, amount) {
// Stripe 退款
}
async _logRefund(order, result) {
console.log('退款日志:', order, result);
}
}
export default PaymentFacade;
// 使用支付外观
import PaymentFacade from './services/PaymentFacade';
function CheckoutPage() {
const [paying, setPaying] = React.useState(false);
const paymentFacade = new PaymentFacade();
const handlePay = async (provider) => {
setPaying(true);
try {
// 简单调用,内部自动处理订单创建、支付、日志、通知等
const result = await paymentFacade.pay(provider, 99.99, {
productId: 'PROD-123',
userId: 'USER-456',
});
console.log('支付成功:', result);
} catch (error) {
console.error('支付失败:', error);
} finally {
setPaying(false);
}
};
return (
<div>
<button onClick={() => handlePay('alipay')} disabled={paying}>
支付宝支付
</button>
<button onClick={() => handlePay('wechat')} disabled={paying}>
微信支付
</button>
<button onClick={() => handlePay('stripe')} disabled={paying}>
Stripe 支付
</button>
</div>
);
}
🎨 在主流框架中的应用
jQuery - 外观模式的典型应用
// jQuery 封装了复杂的 DOM 操作
$('#button').on('click', function() {
$(this).addClass('active');
});
// 等价于原生 JavaScript
document.getElementById('button').addEventListener('click', function() {
this.classList.add('active');
});
React Router - 简化路由配置
// React Router 提供简单的路由配置接口
import { BrowserRouter, Route, Switch } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</BrowserRouter>
);
}
// 内部处理了复杂的路由匹配、历史记录管理等
⚖️ 优缺点分析
✅ 优点
- 简化接口:为复杂系统提供简单接口
- 解耦:客户端与子系统解耦
- 易用性:降低使用难度
- 分层:定义系统中每一层的入口点
❌ 缺点
- 不符合开闭原则:增加新的子系统可能需要修改外观类
- 过度简化:可能隐藏了用户需要的细节
- 单点故障:外观类出问题会影响所有调用
📋 何时使用外观模式
✅ 适合使用的场景
- 为复杂系统提供简单接口
- 需要封装多个步骤的操作
- 解耦客户端和子系统
- 分层架构的入口点
❌ 不适合使用的场景
- 系统本身很简单
- 需要暴露所有细节给客户端
- 子系统经常变化
📝 总结
结构型模式对比
| 模式 | 核心目的 | 使用场景 | 优先级 |
|---|---|---|---|
| 装饰器模式 | 动态添加功能 | HOC、中间件、权限控制 | ⭐⭐⭐⭐⭐ |
| 代理模式 | 控制对象访问 | 懒加载、缓存、权限验证 | ⭐⭐⭐⭐⭐ |
| 适配器模式 | 接口转换 | 第三方库迁移、数据格式转换 | ⭐⭐⭐⭐ |
| 外观模式 | 简化接口 | 复杂系统封装、多步骤操作 | ⭐⭐⭐⭐ |
学习建议
- 装饰器模式:React HOC 和 Hooks 都是装饰器思想,重点掌握
- 代理模式:理解 ES6 Proxy 和 Vue 3 响应式原理
- 适配器模式:在重构和迁移中非常有用
- 外观模式:学会封装复杂逻辑,提供简单 API
四种模式的区别
- 装饰器:不改变接口,增强功能
- 代理:相同接口,控制访问
- 适配器:转换接口,解决不兼容
- 外观:提供新接口,简化复杂系统