想必很多程序员谈到设计模式,肯定是一头雾水,我才没心思管你什么设计模式不设计模式呢!
只要能够实现需要就行,就是一顿搞。其实在编码中合理按照一定设计模式去设计项目代码结构,是能够很好的提高代码的可维护行/可读性,和减少代码的数量。这样性能不就提示了么,自己编码的时候不香,不舒服么?
今天将通过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。但是现在不同了,每个函数彼此独立。
现在,假设我们需要在申请设备后选择地址之前检查库存。在代码的责任链模式中,我们可以通过简单地修改代码来完成需求。
当您负责的模块满足以下条件时,请考虑使用责任链模式。
- 每个过程的代码都可以重用
- 每个过程都有固定的执行顺序
- 每个过程都可以重组
我的翻译计划:虎克小哥哥