工作常用设计模式

概述

设计模式作为软件工程中解决特定问题的经典方案,虽然设计模式很多,对于前端开发来说,实际业务工程项目中常用的就那么几个,见下。

1. 单例模式 (Singleton Pattern)

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

应用场景:全局状态管理、模态框、提示组件等

javascript 复制代码
class AuthService {
  constructor() {
    if (AuthService.instance) {
      return AuthService.instance;
    }
    this.token = null;
    this.user = null;
    AuthService.instance = this;
  }

  login(token, user) {
    this.token = token;
    this.user = user;
  }

  logout() {
    this.token = null;
    this.user = null;
  }

  isAuthenticated() {
    return !!this.token;
  }
}

// 使用示例
const auth1 = new AuthService();
const auth2 = new AuthService();
console.log(auth1 === auth2); // true

auth1.login('token123', { name: 'John' });
console.log(auth2.isAuthenticated()); // true

2. 发布-订阅模式

观察者模式的变体,通过消息通道进行通信,发布者和订阅者不需要知道彼此的存在。

javascript 复制代码
class PubSub {
  constructor() {
    this.topics = {};
    this.subId = 0;
  }

  subscribe(topic, callback) {
    if (!this.topics[topic]) {
      this.topics[topic] = {};
    }
    
    const id = ++this.subId;
    this.topics[topic][id] = callback;
    
    return {
      unsubscribe: () => {
        delete this.topics[topic][id];
        if (Object.keys(this.topics[topic]).length === 0) {
          delete this.topics[topic];
        }
      }
    };
  }

  publish(topic, data) {
    if (!this.topics[topic]) return;
    
    Object.values(this.topics[topic]).forEach(callback => {
      callback(data);
    });
  }
}

// 使用示例
const pubsub = new PubSub();

// 订阅
const subscription = pubsub.subscribe('user.login', (user) => {
  console.log('用户登录:', user);
});

// 发布
pubsub.publish('user.login', { name: 'Alice', id: 1 });

// 取消订阅
subscription.unsubscribe();

3. 工厂模式

创建对象而不暴露创建逻辑,通过工厂方法来创建对象。

应用场景:复杂对象创建、根据不同条件创建不同实例

javascript 复制代码
class Dialog {
  constructor(type, content) {
    this.type = type;
    this.content = content;
  }

  show() {
    console.log(`显示${this.type}对话框: ${this.content}`);
  }
}

class DialogFactory {
  static createDialog(type, content) {
    switch (type) {
      case 'success':
        return new Dialog('成功', content);
      case 'error':
        return new Dialog('错误', content);
      case 'warning':
        return new Dialog('警告', content);
      default:
        throw new Error('未知的对话框类型');
    }
  }
}

// 使用示例
const successDialog = DialogFactory.createDialog('success', '操作成功!');
const errorDialog = DialogFactory.createDialog('error', '操作失败!');

successDialog.show(); // 显示成功对话框: 操作成功!
errorDialog.show();   // 显示错误对话框: 操作失败!

4. 策略模式

定义一系列算法,将它们封装起来,并且使它们可以相互替换。

应用场景:表单验证、支付方式等

javascript 复制代码
// 策略类
const validationStrategies = {
  isRequired: (value) => ({
    isValid: value !== undefined && value !== null && value !== '',
    message: '该字段为必填项'
  }),
  
  isEmail: (value) => ({
    isValid: /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(value),
    message: '请输入有效的邮箱地址'
  }),
  
  minLength: (value, length) => ({
    isValid: value.length >= length,
    message: `长度不能少于${length}个字符`
  }),
  
  maxLength: (value, length) => ({
    isValid: value.length <= length,
    message: `长度不能超过${length}个字符`
  })
};

// 验证器类
class Validator {
  constructor() {
    this.rules = [];
  }

  addRule(field, strategy, ...params) {
    this.rules.push({ field, strategy, params });
    return this;
  }

  validate(data) {
    const errors = {};
    
    this.rules.forEach(rule => {
      const value = data[rule.field];
      const result = validationStrategies[rule.strategy](value, ...rule.params);
      
      if (!result.isValid) {
        if (!errors[rule.field]) {
          errors[rule.field] = [];
        }
        errors[rule.field].push(result.message);
      }
    });
    
    return {
      isValid: Object.keys(errors).length === 0,
      errors
    };
  }
}

// 使用示例
const validator = new Validator()
  .addRule('username', 'isRequired')
  .addRule('username', 'minLength', 3)
  .addRule('email', 'isRequired')
  .addRule('email', 'isEmail');

const data = {
  username: 'ab',
  email: 'invalid-email'
};

const result = validator.validate(data);
console.log(result);

5. 装饰器模式

动态地给对象添加额外的职责,而不改变对象本身。

应用场景:功能扩展、高阶组件、vue2类组件。

javascript 复制代码
// 基础组件
class Component {
  render() {
    return '基础组件';
  }
}

// 装饰器基类
class Decorator {
  constructor(component) {
    this.component = component;
  }

  render() {
    return this.component.render();
  }
}

// 具体装饰器
class BorderDecorator extends Decorator {
  render() {
    return `带有边框的(${super.render()})`;
  }
}

class ColorDecorator extends Decorator {
  constructor(component, color) {
    super(component);
    this.color = color;
  }

  render() {
    return `<span style="color: ${this.color}">${super.render()}</span>`;
  }
}

// 使用示例
let component = new Component();
component = new BorderDecorator(component);
component = new ColorDecorator(component, 'red');

console.log(component.render());
// <span style="color: red">带有边框的(基础组件)</span>

6. 适配器模式

将一个类的接口转换成客户希望的另一个接口。

应用场景:API兼容、第三方库适配、数据格式转换

javascript 复制代码
// 老版本API
class OldAPI {
  request(data) {
    return `老API响应: ${JSON.stringify(data)}`;
  }
}

// 新版本API(期望的接口)
class NewAPI {
  fetch(options) {
    return `新API响应: ${options.url} - ${JSON.stringify(options.body)}`;
  }
}

// 适配器
class APIAdapter {
  constructor(oldAPI) {
    this.oldAPI = oldAPI;
  }

  fetch(options) {
    // 将新API的调用方式适配到老API
    const oldStyleData = {
      method: options.method || 'GET',
      path: options.url,
      body: options.body
    };
    
    return this.oldAPI.request(oldStyleData);
  }
}

// 使用示例
const oldAPI = new OldAPI();
const adapter = new APIAdapter(oldAPI);

// 使用新API的接口调用老API的功能
const result = adapter.fetch({
  url: '/api/users',
  method: 'POST',
  body: { name: 'John' }
});

console.log(result);

7. 代理模式

为其他对象提供一种代理以控制对这个对象的访问。

应用场景:缓存代理、权限控制、虚拟代理、数据验证

javascript 复制代码
// 真实服务
class ImageService {
  constructor(filename) {
    this.filename = filename;
    this.loadImage(); // 模拟耗时的图片加载
  }

  loadImage() {
    console.log(`加载图片: ${this.filename}`);
  }

  display() {
    console.log(`显示图片: ${this.filename}`);
  }
}

// 代理
class ImageProxy {
  constructor(filename) {
    this.filename = filename;
    this.realService = null;
  }

  display() {
    if (!this.realService) {
      this.realService = new ImageService(this.filename);
    }
    this.realService.display();
  }
}

// 使用示例
const image1 = new ImageProxy('photo1.jpg');
const image2 = new ImageProxy('photo2.jpg');

// 只有在真正需要显示时才加载图片
image1.display(); // 第一次调用会加载图片
image1.display(); // 第二次直接使用已加载的图片
相关推荐
前端达人5 小时前
「React实战面试题」useEffect依赖数组的常见陷阱
前端·javascript·react.js·前端框架·ecmascript
开开心心就好5 小时前
PDF清晰度提升工具,让模糊文档变清晰
java·服务器·前端·python·智能手机·pdf·ocr
路修远i5 小时前
灰度和红蓝区
前端
路修远i5 小时前
cursor rules 实践
前端·cursor
路修远i5 小时前
前端-跨域梳理
前端
dreams_dream5 小时前
树形表格示例
javascript·vue.js·elementui
路修远i5 小时前
npm发包
前端
Nan_Shu_6145 小时前
学习:uniapp全栈微信小程序vue3后台 (24)
前端·学习·微信小程序·小程序·uni-app