不论是每次优化代码时还是找工作面试时,都会重新准备一遍设计模式这块的知识点。设计模式我是学了忘了忘了学,每次费劲写了demo,自认为掌握了,结果还是在工作中忘记了或者觉得用起来不称手。时间一长又不记得啥了。所以这次我打算慢慢来,就记一些简单抽象的概念,以及简单的代码实现。 路要一步不走,饭要一口口吃,不急不急。
单例模式
避免重复创建相同的对象实例。在js这种单线程的环境中,单例模式很好实现,如java,则需要避免多线程环境下可能重复创建对象实例的问题。
用途
- 如有全局操作的需求可考虑单例,创建唯一的全局操作入口,确保系统状态唯一。
- 避免频繁创建对象引起的高消耗,阻塞。比如移动端读取缓存,Node对系统文件的操作等,都应尽量避免重复的创建对象,避免多余的系统消耗。
实现方式
实现方式:闭包。我发现闭包真是一个好东西,既能保留住数据状态,又能起到私有变量的作用,避免外界的访问。很多库和设计模式都涉及到闭包。第一个想起来的是react-redux,前两个月刚捋了一遍。
DanliProxyFunc = function (Fn) {
let instance;
return function DanliProxy(name) {
if (!instance) {
instance = new Fn(name);
}
return instance;
};
};
function Cat(name) {
this.name = name;
}
Cat.prototype.getName = function () {
return this.name;
};
let SingleCat = DanliProxyFunc(Cat);
let cat1 = new SingleCat("123");
let cat2 = new SingleCat("234");
console.log(a === b);//true
组合模式
将类似的动作组合在一起,搭积木。几个小模块组合成一个大模块,几个大模块组合成更大的模块。 我最近使用的小米之家全家桶,就是一个组合模式。每个独立的场景,比如开灯、打开小爱同学、打开电视,这些都是独立的动作。但将他们组合到一起就形成了一个特定的场景。比如我回家打开门后,自动触发上述的多组动作。那么回家这个场景就是一个大模块。
用途
- 可以模块化多个相似的动作,形成可复用的大模块(我习惯叫它场景)
- 如果一个场景下的动作组合需要变换,则只需增删这个场景下的动作即可,不需要改动使用场景的代码。做到场景和使用场景的分离。
function ZH(){
return {
commandList:[],
add:function(command){
this.commandList.push(command);
},
excude:function(){
this.commandList.forEach(command=>{
command.excude();
})
}
}
}
let command1 = {
excude:()=>console.log('打开门窗')
}
let command2 = {
excude:()=>console.log('打开窗帘')
}
let command3 = {
excude:()=>console.log('打开台灯')
}
let command4 = {
excude:()=>console.log('打开水龙头')
}
let command5 = {
excude:()=>console.log('打开空调')
}
let command6 = {
excude:()=>console.log('打开电视')
}
let command7 = {
excude:()=>console.log('打开电扇')
}
let ZH1 = ZH();
ZH1.add(command1);
ZH1.add(command2);
ZH1.add(command3);
ZH1.add(command4);
let ZH2 = ZH();
ZH1.add(command5);
ZH1.add(command6);
ZH1.add(command7);
let ZH_SUPER = ZH();
ZH_SUPER.add(ZH1);
ZH_SUPER.add(ZH2);
ZH_SUPER.excude();
享元模式 Flyweight
享元,共享某一个变量、方法等。减少不必要的资源创建。比如线程池、重复利用回收的资源。
用途
- 减少内存开支
- 因为直接使用已经创建的,状态处于空闲的对象,所以性能较好。
实现方式
function threadFactory(createThreadMethod) {
let list = [];
return {
create: function(name) {
return list.length === 0 ? createThreadMethod(name) : list.shift();
},
recover: function(thread) {
list.push(thread);
}
};
}
function Thread(name) {
this.name = name;
}
Thread.prototype.getName = function() {
return this.name;
};
let threadP = threadFactory((name) => {
return new Thread(name);
});
let thread1 = threadP.create('thread1');
let thread2 = threadP.create('thread2');
let thread3 = threadP.create('thread3');
threadP.recover(thread1);//回收线程1
let thread4 = threadP.create('thread4');//不会创建,直接复用线程1
//输出:thread1 thread2 thread3 thread1
console.log(
thread1.getName(),
thread2.getName(),
thread3.getName(),
thread4.getName()
);
职责链模式
链,说明是事件发起者和事件消化者是通过一条链去链接的。就像现实中的一条环环相扣的铁链,中间随意拆除铁链中的几个环节,也不会影响发起者和消化者之间的通信,只是影响了中间的处理环节——少了拆除的环节的处理。 需要注意的一点是,这里的每个环(处理函数)的输入和输出的类型如果都保持一致,如huan1(number)=> object,huan2(number)=> object...这样的话,随意拆除则不会影响后面的环的处理。如果不能一致,则需要注意环节之间输入输出类型的转换。 比如promise的then,我们可以连接多个then去处理上一个then输出的对象。这个就是一个职责链的体现。
用途
- 如上面所说,这种模式,很明显的减少了发起者和消化者之间的逻辑耦合。
- 处理流程的变更,只需在链子上增删改而已,无需修改发起者和消化者的处理逻辑。
- 处理流程直观,后期维护很方便。
/**
场景:最近很穷,我去向朋友借钱,每个朋友的接受程度不一样,根据我想借的钱数,
会有不同的朋友借给我。那最简单的逻辑就是写一堆ifelse的逻辑分支。
那如果是用职责链写,则如下所示:
**/
/**
* 将不同逻辑分别写成独立的函数
* 再写一个chain函数去串联起他们
* 如果中间改变,直接增删改函数,chain的顺序 即可
* */
function borrow1000(money) {
if (money < 1000 && money > 0) {
console.log("找小李");
} else {
return "next";
}
}
function borrow2000(money) {
if (money < 2000 && money > 1000) {
console.log("找小孙");
} else {
return "next";
}
}
function borrow3000(money) {
if (money < 3000 && money > 2000) {
console.log("找小钱");
} else {
return "next";
}
}
function borrow4000(money) {
if (money < 4000 && money > 3000) {
console.log("找小S");
} else {
return "next";
}
}
function borrowNo(money) {
console.log("别找了 没人借给你");
}
function Chain(fn) {
this.fn = fn;
this.nextChain = null;
}
Chain.prototype.process = function(money) {
let ret = this.fn(money);
if (ret === "next") {
this.nextChain.process(money);
}
};
Chain.prototype.next = function(chain) {
this.nextChain = chain;
return chain;
};
let c1 = new Chain(borrow1000);
let c2 = new Chain(borrow2000);
let c3 = new Chain(borrow3000);
let c4 = new Chain(borrow4000);
let no = new Chain(borrowNo);
c1.next(c2)
.next(c3)
.next(c4)
.next(no);
c1.process(2500);
这样,我可以随意删除或者增加中间的环(处理函数)。