手把手教你项目中使用:JavaScript设计模式

3,867 阅读6分钟

想必很多程序员谈到设计模式,肯定是一头雾水,我才没心思管你什么设计模式不设计模式呢!

只要能够实现需要就行,就是一顿搞。其实在编码中合理按照一定设计模式去设计项目代码结构,是能够很好的提高代码的可维护行/可读性,和减少代码的数量。这样性能不就提示了么,自己编码的时候不香,不舒服么?

今天将通过4个实际案例学习前端的设计模式。下面是常见的四种设计模式

  • 策略模式
  • 发布-订阅模式
  • 装饰器模式
  • 责任链模式

策略模式

假设我们有一个要求,当用户试图打开一个页面时,只有满足以下条件才能看到正确的内容:

  • 该用户是该站点的注册用户
  • 用户等级不小于1
  • 用户必须是前端开发工程师
  • 用户类型是活跃用户

现在,我们需要编写判断逻辑以确保只有合适的用户才能看到内容。你是做什么?许多新手程序员可能只是选择了“ if-else”并编写如下代码:

function checkAuth(data) {
  if (data.role !== 'registered') {
    console.log('如果没用注册');
    return false;
  }
  if (data.grade < 1) {
    console.log("用户级别小于1");
    return false;
  }
  if (data.job !== 'FE') {
    console.log('用户不是前端开发工程师');
    return false;
  }
  if (data.type !== 'active user') {
    console.log('用户不是活动用户');
    return false;
  }
}

想必你之前都写过这样的代码吧,但是它很明显存在一下几个问题:

  • checkAuth 函数代码臃肿
  • 每个判断功能都不能重用
  • 违反开闭原则

那么我们如何解决这个问题呢?这就是策略模式起作用的地方。

它是一种设计模式,允许封装用于特定任务的替代算法。它定义了一系列算法,并以一种在运行时可以互换的方式封装它们,而不会受到其他的干扰影响。

现在,让我们使用策略模式来改进先前的代码。

const jobList = ['FE', 'BE'];
const strategies = {
  checkRole: function(value) {
    if (value === 'registered') {
      return true;
    }
    return false;
  },
  checkGrade: function(value) {
    if (value >= 1) {
      return true;
    }
    return false;
  },
  checkJob: function(value) {
    if (jobList.indexOf(value) > 1) {
      return true;
    }
    return false;
  },
  checkType: function(value) {
    if (value === 'active user') {
      return true;
    }
    return false;
  }
};
const Validator = function() {
  // 缓存
  this.cache = [];
// 添加缓存
  this.add = function(value, method) {
    this.cache.push(function() {
      return strategies[method](value);
    });
  };
// 检测是否存在缓存
  this.check = function() {
    for (let i = 0; i < this.cache.length; i++) {
      let valiFn = this.cache[i];
      var data = valiFn();
      if (!data) {
        return false;
      }
    }
    return true;
  };
};

这样的代码是不是看起来很舒服,一个对象实现一个具体的功能,然后一个对象里面具体拆分功能的细节点实现

现在我们再来实现之前的需求

var compose1 = function() {
  var validator = new Validator();
  const data1 = {
    role: 'register',
    grade: 3,
    job: 'FE',
    type: 'active user'
  };
  validator.add(data1.role, 'checkRole');
  validator.add(data1.grade, 'checkGrade');
  validator.add(data1.type, 'checkType');
  validator.add(data1.job, 'checkJob');
const result = validator.check();
  return result;
};

我们可以看到,通过应用策略模式,我们的代码变得更加可维护。现在,您可以考虑将策略模式应用于自己的项目,例如在处理表单验证时。

当您负责的模块基本满足以下条件时,您可以考虑使用策略模式来优化代码。

  • 每个判断条件下的策略都是独立且可重用的
  • 该策略的内部逻辑相对复杂
  • 策略需要灵活组合

发布-订阅模式

现在让我们看另一个需求:当用户成功完成一个应用程序时,后台需要触发相应的订单,消息和审核模块。

您将如何编码?许多程序员可能会这样写:

虎克小哥哥
虎克小哥哥

您将如何编码?许多程序员可能会这样写:

function applySuccess() {
  // 通知消息中心获取最新内容
  MessageCenter.fetch();
  // 更新订单信息
  Order.update();
  // 通知负责人审核
  Checker.alert();
}

随着涉及到越来越多的模块,我们的代码变得越来越肿,难以维护。那就是发布和订阅模型可以节省灾难的时候。

虎克小哥哥
虎克小哥哥

您是否对EventEmitter熟悉?是的,很多面试都会问的,对吗?

发布-订阅是一种消息传递范例,其中消息的发布者不直接将消息发送给特定的订阅者,而是通过消息通道进行广播,订阅者可以通过订阅获得他们想要的消息。 首先,让我们编写一个EventEmit函数:

const EventEmit = function(){ 
  this.events = {}; 
  this.on = function(name,cb){ 
    if(this.events [name]){ 
      this.events [name] .push(cb); 
    } else { 
      this.events [name] = [cb]; 
    } 
  }; 
  this.trigger = function(name,... arg){ 
    if(this.events [name]){ 
      this.events [name] .forEach(eventListener => { 
        eventListener(... arg); 
      }); 
    } 
  }; 
};

上面我们写了一个EventEmit,然后我们的代码可以更改为:

let event = new EventEmit();
MessageCenter.fetch(){ 
  event.on('success',()=> { 
    console.log('通知消息中心获取最新内容'); 
  }); 
} 
Order.update(){ 
  event.on('success',()=> { 
    console.log('更新订单信息'); 
  }); 
} 
Checker.alert(){ 
  event.on('success',()=> { 
    console.log('通知负责人审核'); 
  }); 
}
event.trigger('success');

这样是不是更好?所有事件彼此独立。我们可以随时添加,修改和删除事件,而不会影响其他模块。 当您负责一个基本满足以下条件的模块时,您可以考虑使用发布-订阅模式。

装饰器模式

现在让我们直接看一个例子。

正如任何了解React的人所知道的那样,高阶组件实际上只是一个函数。它接受一个组件作为参数并返回一个新的组件。 因此,让我们编写一个高阶组件HOC,并用它来装饰TargetComponent。

import React from 'react';
const yellowHOC = WrapperComponent => {
  return class extends React.Component {
    render() {
      <div style={{ backgroundColor: 'yellow' }}>
        <WrapperComponent {...this.props} />
      </div>;
    }
  };
};
export default yellowHOC;

在上面的示例中,我们设计了组件yellowHOC来包装其他组件。这是装饰器模式。 如有任何疑问,让我们看看装饰器模式的另一个示例。

//Jon最初是说中文的人
const jonWrite = function() {
  this.writeChinese = function() {
    console.log('我只能写中文');
  };
};
//通过装饰器
const Decorator = function(old) {
  this.oldWrite = old.writeChinese;
  this.writeEnglish = function() {
    console.log('给他写英语的能力');
  };
  this.newWrite = function() {
    this.oldWrite();
    this.writeEnglish();
  };
};
const oldJonWrite = new jonWrite();
const decorator = new Decorator(oldJonWrite);
decorator.newWrite();

看起来已经满足了要求,但实际上,上述缺点非常大:

我们的购买过程可能会发生变化,例如添加库存检查过程。然后,您必须彻底更改原始代码,这很难维护代码设计。

在这一点上,我们可以考虑使用责任链模式。

我们可以这样重写代码:

const Chain = function(fn){ 
  this.fn = fn; 
  this.setNext = function(){}
  this.run = function(){} 
}

const applyDevice = function(){
} 
const chainApplyDevice = new Chain(applyDevice);
const selectAddress = function(){

} 
const chainSelectAddress = new Chain(selectAddress);
const selectChecker = function(){

} 
const chainSelectChecker = new Chain(selectChecker);

chainApplyDevice.setNext(chainSelectAddress).setNext(chainSelectChecker); 
chainApplyDevice.run();

有什么好处?我们要做的第一件事是解耦节点,然后做这件事的方式是在函数A中调用函数B,然后在函数B中调用函数C。但是现在不同了,每个函数彼此独立。

现在,假设我们需要在申请设备后选择地址之前检查库存。在代码的责任链模式中,我们可以通过简单地修改代码来完成需求。

当您负责的模块满足以下条件时,请考虑使用责任链模式。

  • 每个过程的代码都可以重用
  • 每个过程都有固定的执行顺序
  • 每个过程都可以重组

我的翻译计划:虎克小哥哥

原文:medium.com/fedever/4-u…