常用设计模式总结

前端开发中常用的设计模式及使用场景

问题

常用设计模式有哪些?分别在什么场景下使用?

解答

1. 单例模式

确保一个类只有一个实例。

使用场景:全局状态管理、弹窗组件、登录框

// 单例模式
class Singleton {
  static instance = null;
  
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true

// 实际应用:全局 Modal
const Modal = (function() {
  let instance = null;
  
  return function() {
    if (!instance) {
      instance = document.createElement('div');
      instance.className = 'modal';
      document.body.appendChild(instance);
    }
    return instance;
  };
})();

2. 工厂模式

通过工厂函数创建对象,隐藏创建细节。

使用场景:根据条件创建不同类型的对象

// 简单工厂
function createUser(role) {
  const roles = {
    admin: { name: 'Admin', permissions: ['read', 'write', 'delete'] },
    editor: { name: 'Editor', permissions: ['read', 'write'] },
    guest: { name: 'Guest', permissions: ['read'] }
  };
  return roles[role] || roles.guest;
}

const admin = createUser('admin');
const guest = createUser('guest');

// React 中的工厂模式
function createComponent(type, props) {
  const components = {
    button: Button,
    input: Input,
    select: Select
  };
  const Component = components[type];
  return <Component {...props} />;
}

3. 观察者模式

对象间一对多的依赖关系,状态变化时通知所有依赖者。

使用场景:事件系统、数据绑定、消息通知

// 观察者模式
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  // 订阅
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return this;
  }
  
  // 发布
  emit(event, ...args) {
    const callbacks = this.events[event];
    if (callbacks) {
      callbacks.forEach(cb => cb(...args));
    }
    return this;
  }
  
  // 取消订阅
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
    return this;
  }
}

// 使用
const emitter = new EventEmitter();
emitter.on('login', user => console.log(`${user} logged in`));
emitter.emit('login', 'John'); // John logged in

4. 发布订阅模式

与观察者模式类似,但通过事件中心解耦。

使用场景:跨组件通信、微前端通信

// 发布订阅(有事件中心)
class PubSub {
  static events = {};
  
  static subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    // 返回取消订阅函数
    return () => this.unsubscribe(event, callback);
  }
  
  static publish(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(cb => cb(data));
    }
  }
  
  static unsubscribe(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}

// 组件 A 订阅
const unsubscribe = PubSub.subscribe('update', data => {
  console.log('Received:', data);
});

// 组件 B 发布
PubSub.publish('update', { id: 1, name: 'test' });

5. 策略模式

定义一系列算法,封装起来可互相替换。

使用场景:表单验证、价格计算、权限判断

// 策略模式 - 表单验证
const strategies = {
  required: value => value.trim() !== '' || '必填项',
  minLength: (value, len) => value.length >= len || `最少${len}个字符`,
  maxLength: (value, len) => value.length <= len || `最多${len}个字符`,
  email: value => /^\S+@\S+\.\S+$/.test(value) || '邮箱格式错误',
  phone: value => /^1[3-9]\d{9}$/.test(value) || '手机号格式错误'
};

function validate(value, rules) {
  for (const rule of rules) {
    const [name, ...args] = rule.split(':');
    const result = strategies[name](value, ...args);
    if (result !== true) return result;
  }
  return true;
}

// 使用
validate('', ['required']); // '必填项'
validate('abc', ['required', 'minLength:6']); // '最少6个字符'
validate('test@example.com', ['required', 'email']); // true

6. 代理模式

为对象提供代理,控制对原对象的访问。

使用场景:懒加载、缓存、权限控制、数据劫持

// 代理模式 - 缓存代理
function createCachedRequest(request) {
  const cache = new Map();
  
  return async function(url) {
    if (cache.has(url)) {
      console.log('From cache');
      return cache.get(url);
    }
    const result = await request(url);
    cache.set(url, result);
    return result;
  };
}

const cachedFetch = createCachedRequest(fetch);

// Vue 3 响应式原理 - Proxy
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      console.log(`get ${key}`);
      return target[key];
    },
    set(target, key, value) {
      console.log(`set ${key} = ${value}`);
      target[key] = value;
      return true;
    }
  });
}

const state = reactive({ count: 0 });
state.count; // get count
state.count = 1; // set count = 1

7. 装饰器模式

动态给对象添加额外职责。

使用场景:日志记录、性能统计、权限校验

// 装饰器模式 - 函数装饰
function withLog(fn) {
  return function(...args) {
    console.log(`调用 ${fn.name},参数:`, args);
    const result = fn.apply(this, args);
    console.log(`返回:`, result);
    return result;
  };
}

function add(a, b) {
  return a + b;
}

const loggedAdd = withLog(add);
loggedAdd(1, 2);
// 调用 add,参数: [1, 2]
// 返回: 3

// TypeScript 装饰器
function log(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`${name} called`);
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @log
  add(a, b) {
    return a + b;
  }
}

8. 适配器模式

将一个接口转换成另一个接口。

使用场景:接口兼容、数据格式转换

// 适配器模式 - API 数据适配
// 后端返回的数据
const apiData = {
  user_name: 'john',
  user_age: 25,
  created_at: '2024-01-01'
};

// 适配器:转换为前端需要的格式
function adaptUser(data) {
  return {
    name: data.user_name,
    age: data.user_age,
    createdAt: new Date(data.created_at)
  };
}

const user = adaptUser(apiData);
// { name: 'john', age: 25, createdAt: Date }

// 适配不同的请求库
class HttpAdapter {
  constructor(httpLib) {
    this.http = httpLib;
  }
  
  get(url) {
    // 统一接口,内部适配不同库
    if (this.http.request) {
      return this.http.request({ url, method: 'GET' });
    }
    return this.http.get(url);
  }
}

关键点

  • 单例模式:全局唯一实例,适用于弹窗、Store
  • 工厂模式:封装创建逻辑,根据条件返回不同对象
  • 观察者/发布订阅:解耦事件发送者和接收者,Vue/React 事件系统
  • 策略模式:算法封装可替换,消除 if-else,适用于表单验证
  • 代理模式:控制访问,Vue 3 响应式、缓存、懒加载
  • 装饰器模式:不修改原对象增强功能,日志、权限
  • 适配器模式:接口转换,API 数据格式化