您现在的位置是:网站首页 > 如何选择合适的设计模式文章详情

如何选择合适的设计模式

理解设计模式的核心概念

设计模式不是银弹,而是针对特定问题的解决方案模板。在JavaScript中,设计模式的应用需要考虑语言特性——原型继承、函数一等公民、动态类型等。每个模式都有其适用场景,错误的选择可能导致代码过度复杂化。

观察者模式适合处理对象间一对多的依赖关系。比如实现一个事件系统:

class EventEmitter {
  constructor() {
    this.listeners = {};
  }
  
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }
  
  emit(event, ...args) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(cb => cb(...args));
    }
  }
}

分析问题领域

选择模式前必须明确待解决的问题。创建型?结构型?还是行为型问题?例如表单验证场景:

  • 如果验证规则频繁变化,策略模式更合适
  • 需要组合多个验证器时,组合模式可能更好
  • 验证过程涉及多个步骤,可以考虑责任链模式
// 策略模式实现验证器
const validators = {
  required: value => !!value.trim(),
  minLength: (value, length) => value.length >= length,
  email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
};

function validate(formData, rules) {
  return Object.keys(rules).every(field => {
    return rules[field].every(rule => {
      const [validator, ...args] = rule.split(':');
      return validators[validator](formData[field], ...args);
    });
  });
}

评估代码变化方向

好的模式选择应该面向可能的变化。考虑电商系统中的折扣计算:

  • 如果折扣类型固定,简单条件判断足够
  • 预期会新增折扣类型,策略模式更合适
  • 需要组合多种折扣时,装饰器模式可能更好
// 策略模式实现折扣计算
const discountStrategies = {
  fixed: (amount, discount) => amount - discount,
  percentage: (amount, percent) => amount * (1 - percent/100),
  seasonal: (amount, season) => {
    const factors = { summer: 0.8, winter: 0.7 };
    return amount * (factors[season] || 1);
  }
};

function calculatePrice(amount, strategy, ...args) {
  return discountStrategies[strategy](amount, ...args);
}

考虑性能影响

某些模式会引入额外开销。例如:

  • 享元模式通过共享减少内存使用,但增加查找复杂度
  • 代理模式增强控制但可能影响性能
  • 观察者模式在事件密集时可能导致性能问题

虚拟滚动场景适合享元模式:

class FlyweightList {
  constructor(itemHeight, renderItem) {
    this.itemHeight = itemHeight;
    this.renderItem = renderItem;
    this.visibleItems = new Map();
  }
  
  update(container, scrollTop, data) {
    const startIdx = Math.floor(scrollTop / this.itemHeight);
    const endIdx = Math.min(
      startIdx + Math.ceil(container.clientHeight / this.itemHeight),
      data.length - 1
    );
    
    // 回收不可见项
    this.visibleItems.forEach((node, idx) => {
      if (idx < startIdx || idx > endIdx) {
        container.removeChild(node);
        this.visibleItems.delete(idx);
      }
    });
    
    // 添加新可见项
    for (let i = startIdx; i <= endIdx; i++) {
      if (!this.visibleItems.has(i)) {
        const node = this.renderItem(data[i]);
        node.style.position = 'absolute';
        node.style.top = `${i * this.itemHeight}px`;
        container.appendChild(node);
        this.visibleItems.set(i, node);
      }
    }
  }
}

团队熟悉度因素

选择模式时需要考虑团队的技术储备:

  • 熟悉度高的模式实现和维护成本更低
  • 新颖模式可能带来长期收益,但短期学习成本高
  • 文档和示例的完整性影响模式采用

例如,React生态中常见的高阶组件模式:

function withLoading(WrappedComponent) {
  return function EnhancedComponent({ isLoading, ...props }) {
    return isLoading ? <div className="loader" /> : <WrappedComponent {...props} />;
  };
}

// 使用
const UserProfileWithLoading = withLoading(UserProfile);

模式组合的可能性

复杂场景往往需要组合多个模式:

  • MVC架构结合观察者、策略、组合模式
  • Redux实现中可见单例、观察者、装饰器模式的组合
  • 现代前端框架常混合工厂、代理、装饰器等模式

状态管理库的典型实现:

// 结合单例和观察者模式
class Store {
  static instance;
  
  constructor(reducer) {
    if (Store.instance) {
      return Store.instance;
    }
    this.state = reducer(undefined, {});
    this.listeners = [];
    this.reducer = reducer;
    Store.instance = this;
  }
  
  dispatch(action) {
    this.state = this.reducer(this.state, action);
    this.listeners.forEach(listener => listener());
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
  
  getState() {
    return this.state;
  }
}

JavaScript特定模式选择

某些模式在JavaScript中有特殊实现方式:

  • 模块模式替代传统单例
  • 原型继承实现享元模式
  • 闭包实现私有变量

模块模式示例:

const counterModule = (() => {
  let count = 0;
  
  const increment = () => ++count;
  const reset = () => count = 0;
  const getCount = () => count;
  
  return {
    increment,
    reset,
    getCount
  };
})();

// 使用
counterModule.increment();
console.log(counterModule.getCount());

避免模式滥用

模式滥用常见表现:

  • 简单问题使用复杂模式
  • 强行套用模式导致代码晦涩
  • 忽视语言特性而机械应用模式

例如,不需要工厂模式的简单对象创建:

// 过度设计
class UserFactory {
  static createUser(type) {
    switch(type) {
      case 'admin': return new Admin();
      case 'customer': return new Customer();
      default: throw new Error('Invalid user type');
    }
  }
}

// 更简单的实现
function createUser(type, props) {
  const userTypes = {
    admin: Admin,
    customer: Customer
  };
  const UserClass = userTypes[type];
  if (!UserClass) throw new Error('Invalid user type');
  return new UserClass(props);
}

重构现有代码的模式引入

逐步引入模式的方法:

  1. 识别代码中的痛点
  2. 选择针对性解决这些痛点的模式
  3. 小范围重构验证效果
  4. 逐步扩大应用范围

例如将回调地狱改为命令模式:

// 重构前
function processOrder(orderId, callback) {
  fetchOrder(orderId, (order) => {
    validateOrder(order, (isValid) => {
      if (isValid) {
        updateInventory(order, () => {
          sendConfirmation(order, callback);
        });
      }
    });
  });
}

// 重构后
class OrderProcessor {
  constructor() {
    this.commands = [];
  }
  
  addCommand(command) {
    this.commands.push(command);
  }
  
  execute(order, callback) {
    let current = 0;
    const next = () => {
      if (current < this.commands.length) {
        this.commands[current++](order, next);
      } else {
        callback();
      }
    };
    next();
  }
}

// 使用
const processor = new OrderProcessor();
processor.addCommand(fetchOrder);
processor.addCommand(validateOrder);
processor.addCommand(updateInventory);
processor.addCommand(sendConfirmation);
processor.execute(orderId, () => console.log('Done'));

设计模式与架构模式的关系

区分设计模式与架构模式:

  • 设计模式解决微观层面的代码组织问题
  • 架构模式关注宏观应用结构
  • 两者可以协同工作

前端常见的架构模式组合:

// MV*架构示例
class Model {
  constructor(data) {
    this.data = data;
    this.listeners = [];
  }
  
  set(key, value) {
    this.data[key] = value;
    this.notify();
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
  }
  
  notify() {
    this.listeners.forEach(l => l(this.data));
  }
}

class View {
  constructor(template) {
    this.template = template;
    this.element = document.createElement('div');
  }
  
  render(data) {
    this.element.innerHTML = this.template(data);
    return this.element;
  }
}

class Controller {
  constructor(model, view) {
    this.model = model;
    this.view = view;
    this.model.subscribe(data => this.updateView(data));
  }
  
  updateView(data) {
    document.body.appendChild(this.view.render(data));
  }
  
  handleInput(key, value) {
    this.model.set(key, value);
  }
}

现代JavaScript特性对模式的影响

ES6+特性改变了某些模式的实现方式:

  • Class语法替代构造函数模式
  • 模块系统替代命名空间模式
  • Proxy替代部分装饰器模式实现
  • Symbol实现更好的私有成员

使用Proxy实现数据绑定:

function createReactiveObject(initialState, onChange) {
  return new Proxy(initialState, {
    set(target, property, value) {
      const oldValue = target[property];
      target[property] = value;
      if (oldValue !== value) {
        onChange(property, value, oldValue);
      }
      return true;
    }
  });
}

const state = createReactiveObject(
  { count: 0 },
  (key, newVal, oldVal) => console.log(`${key} changed from ${oldVal} to ${newVal}`)
);

state.count = 1; // 触发日志输出

测试驱动下的模式选择

测试需求影响模式选择:

  • 依赖注入模式提高可测试性
  • 策略模式便于单元测试替换
  • 工厂模式简化测试替身创建

可测试的服务层实现:

// 数据服务抽象
class DataService {
  constructor(fetcher = fetch) {
    this.fetcher = fetcher;
  }
  
  async getUsers() {
    const response = await this.fetcher('/api/users');
    return response.json();
  }
}

// 测试时
const mockFetcher = () => Promise.resolve({
  json: () => Promise.resolve([{ id: 1, name: 'Test' }])
});

const testService = new DataService(mockFetcher);
testService.getUsers().then(users => console.log(users));

性能分析与模式调整

通过性能分析优化模式选择:

  • 内存分析发现对象创建开销 → 考虑享元模式
  • 函数调用频繁 → 考虑策略模式缓存
  • 复杂条件判断 → 状态模式可能更优

使用memoization优化计算:

function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// 使用
const expensiveCalculation = memoize((a, b) => {
  console.log('Calculating...');
  return a * b + a / b;
});

console.log(expensiveCalculation(2, 3)); // 计算
console.log(expensiveCalculation(2, 3)); // 从缓存读取

浏览器环境下的特殊考量

前端特有因素影响模式选择:

  • DOM操作成本高 → 适合命令模式封装
  • 事件处理复杂 → 观察者/中介者模式
  • 异步操作多 → Promise/async模式组合

批量DOM更新优化:

class DOMBatchUpdater {
  constructor() {
    this.updates = [];
    this.batchTimer = null;
  }
  
  queueUpdate(element, property, value) {
    this.updates.push({ element, property, value });
    if (!this.batchTimer) {
      this.batchTimer = requestAnimationFrame(() => this.flushUpdates());
    }
  }
  
  flushUpdates() {
    const updatesByElement = new Map();
    
    this.updates.forEach(({ element, property, value }) => {
      if (!updatesByElement.has(element)) {
        updatesByElement.set(element, {});
      }
      updatesByElement.get(element)[property] = value;
    });
    
    updatesByElement.forEach((props, element) => {
      Object.assign(element.style, props);
    });
    
    this.updates = [];
    this.batchTimer = null;
  }
}

// 使用
const updater = new DOMBatchUpdater();
updater.queueUpdate(divEl, 'color', 'red');
updater.queueUpdate(divEl, 'backgroundColor', 'white');

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步