每天一个高级前端知识 - Day 25

0 阅读4分钟

每天一个高级前端知识 - Day 25

今日主题:前端设计模式 - 构建可维护的大型应用

核心概念:设计模式是解决特定问题的成熟方案,而非教条

在框架林立的今天,设计模式依然是理解复杂系统、构建可维护应用的元技能

🔬 前端常用设计模式全景图

创建型模式 ── 如何创建对象
├── 工厂模式 (Factory)
├── 单例模式 (Singleton)
├── 建造者模式 (Builder)
└── 原型模式 (Prototype)

结构型模式 ── 如何组织对象
├── 适配器模式 (Adapter)
├── 装饰器模式 (Decorator)
├── 代理模式 (Proxy)
└── 组合模式 (Composite)

行为型模式 ── 对象如何协作
├── 观察者模式 (Observer)
├── 策略模式 (Strategy)
├── 状态模式 (State)
├── 命令模式 (Command)
└── 职责链模式 (Chain of Responsibility)

🏭 创建型模式

// ============ 1. 工厂模式 ============
// 场景:根据不同类型创建不同的 UI 组件
interface Button {
  render(): void;
  onClick(): void;
}

class PrimaryButton implements Button {
  render() { console.log('渲染主要按钮'); }
  onClick() { console.log('主要按钮点击'); }
}

class SecondaryButton implements Button {
  render() { console.log('渲染次要按钮'); }
  onClick() { console.log('次要按钮点击'); }
}

class DangerButton implements Button {
  render() { console.log('渲染危险按钮'); }
  onClick() { console.log('危险按钮点击'); }
}

// 工厂
class ButtonFactory {
  static createButton(type: 'primary' | 'secondary' | 'danger'): Button {
    switch (type) {
      case 'primary': return new PrimaryButton();
      case 'secondary': return new SecondaryButton();
      case 'danger': return new DangerButton();
      default: throw new Error(`Unknown button type: ${type}`);
    }
  }
}

// 使用
const button = ButtonFactory.createButton('primary');
button.render();

// ============ 2. 单例模式 ============
// 场景:全局状态管理、日志记录器、数据库连接池

class Store {
  private static instance: Store;
  private state: Record<string, any> = {};
  
  private constructor() {} // 私有构造函数,禁止外部实例化
  
  static getInstance(): Store {
    if (!Store.instance) {
      Store.instance = new Store();
    }
    return Store.instance;
  }
  
  set(key: string, value: any) {
    this.state[key] = value;
    this.notify(key, value);
  }
  
  get(key: string) {
    return this.state[key];
  }
  
  private notify(key: string, value: any) {
    console.log(`状态变更: ${key} = ${value}`);
  }
}

// 使用
const store1 = Store.getInstance();
const store2 = Store.getInstance();
console.log(store1 === store2); // true

// React 中的单例模式
// 全局 Toast 管理器
class ToastManager {
  private static instance: ToastManager;
  private toasts: Toast[] = [];
  private listeners: Set<() => void> = new Set();
  
  static getInstance() {
    if (!ToastManager.instance) {
      ToastManager.instance = new ToastManager();
    }
    return ToastManager.instance;
  }
  
  show(message: string, type: 'success' | 'error' | 'info' = 'info') {
    const id = Date.now();
    this.toasts.push({ id, message, type });
    this.notify();
    
    setTimeout(() => {
      this.toasts = this.toasts.filter(t => t.id !== id);
      this.notify();
    }, 3000);
  }
  
  subscribe(listener: () => void) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
  
  private notify() {
    this.listeners.forEach(listener => listener());
  }
  
  getToasts() {
    return this.toasts;
  }
}

// ============ 3. 建造者模式 ============
// 场景:构建复杂的配置对象

class RequestBuilder {
  private url: string = '';
  private method: string = 'GET';
  private headers: Record<string, string> = {};
  private body: any = null;
  private timeout: number = 5000;
  private retries: number = 0;
  
  setUrl(url: string): this {
    this.url = url;
    return this;
  }
  
  setMethod(method: string): this {
    this.method = method;
    return this;
  }
  
  addHeader(key: string, value: string): this {
    this.headers[key] = value;
    return this;
  }
  
  setBody(body: any): this {
    this.body = body;
    return this;
  }
  
  setTimeout(timeout: number): this {
    this.timeout = timeout;
    return this;
  }
  
  setRetries(retries: number): this {
    this.retries = retries;
    return this;
  }
  
  async execute() {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);
    
    try {
      const response = await fetch(this.url, {
        method: this.method,
        headers: this.headers,
        body: this.body ? JSON.stringify(this.body) : null,
        signal: controller.signal
      });
      return response.json();
    } finally {
      clearTimeout(timeoutId);
    }
  }
}

// 使用
const data = await new RequestBuilder()
  .setUrl('https://api.example.com/users')
  .setMethod('POST')
  .addHeader('Authorization', 'Bearer token')
  .addHeader('Content-Type', 'application/json')
  .setBody({ name: 'John', age: 30 })
  .setTimeout(10000)
  .setRetries(3)
  .execute();

🏗️ 结构型模式

// ============ 4. 适配器模式 ============
// 场景:统一不同 API 的响应格式

// 旧 API 格式
interface OldApiResponse {
  user_name: string;
  user_age: number;
  user_email: string;
}

// 新 API 格式
interface NewApiResponse {
  name: string;
  age: number;
  email: string;
}

// 适配器
class ApiAdapter {
  static toNewFormat(oldData: OldApiResponse): NewApiResponse {
    return {
      name: oldData.user_name,
      age: oldData.user_age,
      email: oldData.user_email
    };
  }
  
  static toOldFormat(newData: NewApiResponse): OldApiResponse {
    return {
      user_name: newData.name,
      user_age: newData.age,
      user_email: newData.email
    };
  }
}

// 使用适配器统一数据格式
async function fetchUser(id: string): Promise<NewApiResponse> {
  const response = await fetch(`/api/user/${id}`);
  const data = await response.json();
  
  // 检查响应格式并适配
  if ('user_name' in data) {
    return ApiAdapter.toNewFormat(data);
  }
  return data;
}

// ============ 5. 装饰器模式 ============
// 场景:给函数添加日志、缓存、性能监控等能力

// 日志装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`[${new Date().toISOString()}] 调用 ${propertyKey}(${JSON.stringify(args)})`);
    const result = originalMethod.apply(this, args);
    console.log(`[${new Date().toISOString()}] ${propertyKey} 返回 ${JSON.stringify(result)}`);
    return result;
  };
  
  return descriptor;
}

// 缓存装饰器
function cache(ttl: number = 60000) {
  const cacheMap = new Map();
  
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args: any[]) {
      const key = `${propertyKey}:${JSON.stringify(args)}`;
      const cached = cacheMap.get(key);
      
      if (cached && Date.now() - cached.timestamp < ttl) {
        console.log(`[缓存命中] ${key}`);
        return cached.data;
      }
      
      const result = await originalMethod.apply(this, args);
      cacheMap.set(key, { data: result, timestamp: Date.now() });
      return result;
    };
    
    return descriptor;
  };
}

// 重试装饰器
function retry(maxRetries: number = 3, delay: number = 1000) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args: any[]) {
      let lastError: Error;
      
      for (let i = 0; i <= maxRetries; i++) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          lastError = error as Error;
          console.log(`重试 ${i + 1}/${maxRetries}: ${error}`);
          
          if (i < maxRetries) {
            await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
          }
        }
      }
      
      throw lastError!;
    };
    
    return descriptor;
  };
}

// 使用装饰器
class UserService {
  @log
  @cache(30000) // 缓存30秒
  @retry(3, 1000)
  async getUser(id: string) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
}

// ============ 6. 代理模式 ============
// 场景:图片懒加载、访问控制、虚拟代理

// 虚拟代理:图片懒加载
class LazyImage {
  private img: HTMLImageElement;
  private placeholder: string;
  
  constructor(private src: string, placeholder: string = 'data:image/svg+xml,...') {
    this.placeholder = placeholder;
    this.img = new Image();
  }
  
  render(container: HTMLElement) {
    // 先显示占位图
    const imgElement = document.createElement('img');
    imgElement.src = this.placeholder;
    container.appendChild(imgElement);
    
    // 当图片进入视口时加载真实图片
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        this.img.onload = () => {
          imgElement.src = this.src;
        };
        this.img.src = this.src;
        observer.disconnect();
      }
    });
    
    observer.observe(imgElement);
  }
}

// 保护代理:权限控制
class AdminProxy {
  constructor(private userService: UserService, private userRole: string) {}
  
  async deleteUser(id: string) {
    if (this.userRole !== 'admin') {
      throw new Error('权限不足,只有管理员可以删除用户');
    }
    return this.userService.deleteUser(id);
  }
  
  async updateUser(id: string, data: any) {
    if (this.userRole !== 'admin' && this.userRole !== 'editor') {
      throw new Error('权限不足');
    }
    return this.userService.updateUser(id, data);
  }
}

🔄 行为型模式

// ============ 7. 观察者模式 ============
// 场景:事件总线、状态管理、响应式系统

// 简易事件总线
class EventBus {
  private listeners: Map<string, Set<Function>> = new Map();
  
  on(event: string, callback: Function) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(callback);
    
    // 返回取消订阅函数
    return () => this.off(event, callback);
  }
  
  off(event: string, callback: Function) {
    this.listeners.get(event)?.delete(callback);
  }
  
  emit(event: string, data?: any) {
    this.listeners.get(event)?.forEach(callback => {
      try {
        callback(data);
      } catch (error) {
        console.error(`事件 ${event} 处理失败:`, error);
      }
    });
  }
  
  once(event: string, callback: Function) {
    const wrapper = (data: any) => {
      callback(data);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

// 使用
const eventBus = new EventBus();

// 组件A订阅事件
const unsubscribe = eventBus.on('user-login', (user) => {
  console.log('用户已登录:', user);
});

// 组件B触发事件
eventBus.emit('user-login', { id: 1, name: 'John' });

// 取消订阅
unsubscribe();

// ============ 8. 策略模式 ============
// 场景:表单验证、排序算法、支付方式

// 验证策略
interface ValidationStrategy {
  validate(value: any): { valid: boolean; message?: string };
}

class RequiredStrategy implements ValidationStrategy {
  validate(value: any) {
    const valid = value !== undefined && value !== null && value !== '';
    return { valid, message: valid ? undefined : '此字段为必填项' };
  }
}

class EmailStrategy implements ValidationStrategy {
  private emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  
  validate(value: string) {
    const valid = this.emailRegex.test(value);
    return { valid, message: valid ? undefined : '请输入有效的邮箱地址' };
  }
}

class MinLengthStrategy implements ValidationStrategy {
  constructor(private min: number) {}
  
  validate(value: string) {
    const valid = value && value.length >= this.min;
    return { valid, message: valid ? undefined : `最少需要 ${this.min} 个字符` };
  }
}

class MaxLengthStrategy implements ValidationStrategy {
  constructor(private max: number) {}
  
  validate(value: string) {
    const valid = !value || value.length <= this.max;
    return { valid, message: valid ? undefined : `最多允许 ${this.max} 个字符` };
  }
}

// 验证器上下文
class Validator {
  private strategies: Map<string, ValidationStrategy[]> = new Map();
  
  addStrategy(field: string, strategy: ValidationStrategy) {
    if (!this.strategies.has(field)) {
      this.strategies.set(field, []);
    }
    this.strategies.get(field)!.push(strategy);
  }
  
  validate(data: Record<string, any>) {
    const errors: Record<string, string[]> = {};
    
    for (const [field, strategies] of this.strategies) {
      const fieldErrors: string[] = [];
      
      for (const strategy of strategies) {
        const result = strategy.validate(data[field]);
        if (!result.valid && result.message) {
          fieldErrors.push(result.message);
        }
      }
      
      if (fieldErrors.length > 0) {
        errors[field] = fieldErrors;
      }
    }
    
    return {
      valid: Object.keys(errors).length === 0,
      errors
    };
  }
}

// 使用
const validator = new Validator();
validator.addStrategy('email', new RequiredStrategy());
validator.addStrategy('email', new EmailStrategy());
validator.addStrategy('password', new RequiredStrategy());
validator.addStrategy('password', new MinLengthStrategy(6));
validator.addStrategy('password', new MaxLengthStrategy(20));

const result = validator.validate({
  email: 'invalid-email',
  password: '123'
});

console.log(result);
// { valid: false, errors: { email: ['请输入有效的邮箱地址'], password: ['最少需要 6 个字符'] } }

// ============ 9. 状态模式 ============
// 场景:有限状态机、工作流、订单状态管理

interface OrderState {
  handle(): void;
  cancel(): void;
  getStatus(): string;
}

class PendingState implements OrderState {
  constructor(private order: OrderContext) {}
  
  handle() {
    console.log('订单已支付,进入处理中状态');
    this.order.setState(this.order.processingState);
  }
  
  cancel() {
    console.log('订单已取消');
    this.order.setState(this.order.cancelledState);
  }
  
  getStatus() { return '待支付'; }
}

class ProcessingState implements OrderState {
  constructor(private order: OrderContext) {}
  
  handle() {
    console.log('订单已发货,进入配送状态');
    this.order.setState(this.order.shippingState);
  }
  
  cancel() {
    console.log('处理中的订单无法取消');
  }
  
  getStatus() { return '处理中'; }
}

class ShippingState implements OrderState {
  constructor(private order: OrderContext) {}
  
  handle() {
    console.log('订单已完成');
    this.order.setState(this.order.completedState);
  }
  
  cancel() {
    console.log('配送中的订单无法取消');
  }
  
  getStatus() { return '配送中'; }
}

class CompletedState implements OrderState {
  handle() { console.log('订单已完成,无法变更'); }
  cancel() { console.log('已完成订单无法取消'); }
  getStatus() { return '已完成'; }
}

class CancelledState implements OrderState {
  handle() { console.log('已取消订单无法处理'); }
  cancel() { console.log('订单已取消'); }
  getStatus() { return '已取消'; }
}

class OrderContext {
  pendingState: OrderState;
  processingState: OrderState;
  shippingState: OrderState;
  completedState: OrderState;
  cancelledState: OrderState;
  
  private currentState: OrderState;
  
  constructor() {
    this.pendingState = new PendingState(this);
    this.processingState = new ProcessingState(this);
    this.shippingState = new ShippingState(this);
    this.completedState = new CompletedState(this);
    this.cancelledState = new CancelledState(this);
    
    this.currentState = this.pendingState;
  }
  
  setState(state: OrderState) {
    this.currentState = state;
  }
  
  handle() {
    this.currentState.handle();
  }
  
  cancel() {
    this.currentState.cancel();
  }
  
  getStatus() {
    return this.currentState.getStatus();
  }
}

// 使用
const order = new OrderContext();
console.log(order.getStatus()); // 待支付
order.handle(); // 订单已支付,进入处理中状态
console.log(order.getStatus()); // 处理中
order.handle(); // 订单已发货,进入配送状态
console.log(order.getStatus()); // 配送中
order.cancel(); // 配送中的订单无法取消

🎯 今日挑战

实现一个完整的表单系统,要求:

  1. 使用策略模式实现多种验证规则
  2. 使用观察者模式实现表单联动
  3. 使用代理模式实现表单提交防抖
  4. 使用状态模式管理表单状态(编辑/预览/提交)
  5. 使用建造者模式构建复杂表单配置
// 使用示例
const form = new FormBuilder()
  .addField('username', 'text', { label: '用户名', required: true, minLength: 3 })
  .addField('email', 'email', { label: '邮箱', required: true })
  .addField('age', 'number', { label: '年龄', min: 18, max: 99 })
  .addField('role', 'select', { label: '角色', options: ['admin', 'user', 'guest'] })
  .build();

form.on('change', (data) => console.log('表单数据变更:', data));
form.on('submit', async (data) => {
  await api.submit(data);
});

form.render('#app');

明日预告:前端监控与错误追踪 - 构建企业级监控系统

💡 设计模式箴言:"模式是过往经验的结晶,但过度使用模式会使代码复杂化。保持简单,只在必要时引入模式。"