【设计模式】常用设计模式
生成器模式
单例模式
单例模式要求每次调用都返回同一个对象,惰性单例模式则是只在有调用的情况下才创建单例对象。
例子:登录弹窗。
只在按钮点击的时候创建弹窗的 DOM
,之后 createLogLayer
方法返回的都是同一个对象。
- 单例工厂函数
function singleFactory(fn) {
let result = null;
return function () {
if (!result) {
result = fn.apply(this, arguments);
}
return result;
};
}
- 业务逻辑代码
function createLogLayer() {
var div = document.createElement("div");
div.innerHTML = "我是登录浮窗";
div.style.display = "none";
document.body.appendChild(div);
return div;
}
var createSingleLoginLayer = singleFactory(createLogLayer);
btn.onclick = () => {
let loginLayer = createSingleLoginLayer();
loginLayer.style.display = "block";
};
策略模式
在函数作为一等对象的语言中,策略模式是隐形的。
strategy
就是值为函数的变量。 ——Peter Norvig
策略模式是指通过封装一系列的算法,根据不同的策略调用对应的算法。
和 编写可维护的JavaScript
中提出的隔离应用逻辑有些相似。行为是对指令和处理函数进行分离;目的都是提高 策略
也就是具体处理函数的复用性以及可扩展性。
策略模式就是把逻辑中固定的部分和可变的部分分隔开,让我们能够复用那些固定的部分,同时能够灵活地选课可变的部分。
代理模式
代理模式可以让目标保持单一职责原则,比如一个求乘积函数应该专注于求乘积,添加缓存的操作可以通过添加一个缓存代理来实现。
代理模式就是通过一个中间对象去访问目标对象,而不是直接操作目标对象。
- 保护代理:控制不同权限的对象对目标对象的访问。
- 虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候才去创建。
Vue Store
中对修改数据的操作就是一种保护代理:用户只能通过定义 mutations
和 actions
方法去修改 state
中的数据。
防抖节流也是一种代理。
例子:使用代理缓存求乘积函数。
// 求乘积函数
function mult() {
var result = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
result = result * arguments[i];
}
return result;
}
// 缓存代理函数
var catchProxy = function () {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ",");
if (args in cache) {
return cache[args];
}
return (cache[args] = mult.apply(this, arguments));
};
};
let proxyMult = catchProxy(mult);
proxyMult(1, 2, 3, 4); // 输出:24
proxyMult(1, 2, 3, 4); // 输出:24
保证代理和本体接口的一致性
对于代理对象的使用者而言,代理接收请求的过程应该是透明的,这样做有两个好处。
- 用户可以放心地请求代理,他只关心是否能得到想要的结果。
- 在任何使用本体的地方都可以替换成使用代理。
发布-订阅模式
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。
Vue 的事件总线模式
就是一种发布-订阅模式。
模块之间如果用了太多的全局发布订阅模式来通信,代码就会变得难以维护,因为依赖并不明确,最终会搞不清楚消息来自哪个模块,或者消息会流向哪些模块。
享元模式与对象池
享元模式是一种性能调优的设计模式,主要思路是实现对象共享。
如果一个项目中需要创建非常多的对象,就会占用很多内存,这会导致浏览器性能问题甚至崩溃,如果这些对象有共同性可以尝试将对象共享,减少对象的数量。
对象池也是一种对象共享的方式。这种方式强调将每次创建的对象都缓存起来,如果同时需要三个对象,但是对象池中只有两个,那就需要创建一个新对象,使用完毕后将这个对象也存入对象池中,这时对象池中就有三个对象。
职责链模式
职责链是指将多个处理程序连接到一起,每个处理程序接收同样的参数但是各自实现自己的逻辑。
express
框架的中间件功能就是一种职责链。
例子:下单逻辑
假如有一个下单逻辑,有三种情况:
- 500 元定金订单,购买成功后可以获得 100 元优惠券,未支付定金时转入普通购买,不受库存影响。
- 300 元定金订单,购买成功可以获得 50 元优惠券,未支付定金时转入普通购买,不受库存影响。
- 普通购买,无货时无法购买。
定义三个处理程序,并将其绑定到职责链上:
// orderType 订单类型:1表示500元定金订单、2表示300元定金订单、3表示普通订单。
// pay 定金是否支付。
// stock 是否有货。
function order500(orderType, pay, stock, next) {
if (orderType === 1 && pay) {
console.log("购买成功,获得100元优惠券");
} else {
next();
}
}
function order300(orderType, pay, stock, next) {
if (orderType === 2 && pay) {
console.log("购买成功,获得50元优惠券");
} else {
next();
}
}
function orderNormal(orderType, pay, stock, next) {
if (stock) {
console.log("购买成功");
} else {
console.log("购买失败");
}
}
const orderChain = new Chain();
orderChain.add(order500).add(order300).add(orderNormal); // 在职责链上添加处理程序
orderChain.exec(1, false, true); // 触发职责链执行
Chain
类的实现:
class Chain {
/**
* 事件队列
*/
eventList = [];
/**
* 添加处理程序
* @param {*} fn
* @returns
*/
add(fn) {
if (typeof fn === "function") {
this.eventList.push(fn);
}
return this;
}
/**
* 开始执行
* @param {...any} args
*/
exec(...args) {
if (this.eventList.length > 0) {
const event = this.eventList.shift();
event.call(this, ...args, this.exec.bind(this, ...args));
}
}
}
处理程序可以自己决定是否要继续向下传递、何时向下传递,所以异步节点也是支持的:
// ...
function asyncOrder(orderType, pay, stock, next) {
setTimeout(() => {
console.log("异步节点执行完毕");
next();
}, 1000);
}
// ...
orderChain.add(order500).add(order300).add(asyncOrder).add(orderNormal);
orderChain.exec(1, false, true);
中介者模式
中介者模式可以解决多个对象之前的强耦合关系。
状态模式
状态模式是状态机的一种实现,可以提高状态切换逻辑的可维护性。