前言
一、单例模式
二、策略模式
三、代理模式
四、迭代器模式
五、发布-订阅模式(观察者模式)
六、命令模式
七、组合模式
八、模板方法模式
九、享元模式
十、职责链模式
十一、中介者模式
十二、装饰者模式
十三、状态模式
十四、适配器模式
总结
借着这段时间拜读了曾探大神的《JavaScript设计模式与开发实践》一书。本文以此总结一下JS常见的设计模式与实现方法,主要做一些笔记以方便自己过后复习与加深理解,同时也希望把书中典型例子整理出来和大家分享,共同探讨和进步。
设计模式:在面向对象软件设计过程中针对特定问题的简洁而优雅地解决方案。
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如:线程池、全局缓存、window对象等。
核心逻辑:一个变量来标志是否创建对象,如果是,返回该创建好的对象。
将创建对象和管理单例的职责分布在两个不同的方法中。
let obj;
if (obj) {
obj = xxxxx;
}
例子:
// 核心逻辑
const getSingle = function( fn ) {
let result;
return function() {
return result || ( result = fn.apply(this, arguments) );
}
};
class Singletion {
constructor(name) {
this.name = name;
}
getName() {
alert(this.name);
}
}
const singletion = getSingle(function(name) {
let sing = new Singletion(name);
return sing;
});
singletion('a').getName();
singletion('b').getName();
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
核心逻辑:将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少有两个部分组成。第一个部分是一组策略类,封装了具体的算法,
并负责具体的计算过程。第二个部分是环境类,接收客户的请求,随后把请求委托给某一个策略类。
例子一:
const strategies = {
"S" : function(salary){
return salary * 4;
},
"A" : function(salary){
return salary * 3;
},
"B" : function(salary){
return salary * 2;
}
};
const calculateBonus = function(level, salary){
return strategies[level](salary);
}
console.log(calculateBonus("S", 2000));
例子二:
总结:
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问(如 保护代理、虚拟代理、缓存代理、防火墙代理、远程代理、写时复制代理等)。
例子一(虚拟代理):
const miniConsole = (function() {
const cache = [];
const handler = function(e) {
if(e.keyCode === 113) {
const script = document.createElement('script');
script.onload = function() {
for(let i = 0, fn; fn = cache[i++];){
fn();
}
};
script.src = 'miniConsole.js';
document.getElementsByTagName('head')[0].appendChild(script);
document.body.removeEventListener('keydown', handler);
}
};
document.body.addEventListener('keydown', handler, false);
return {
log: function(){
const args = arguments;
cache.push(function() {
return miniConsole.log.apply(miniConsole, args);
});
}
}
})();
miniConsole.log(11);
例子二(缓存代理):
// 计算乘积
const mult = function() {
let a = 1;
for( let i = 0, l = arguments.length; i < l; i++){
a = a * arguments[i];
}
return a;
};
// 计算加和
const plus = function() {
let a = 1;
for( let i = 0, l = arguments.length; i < l; i++){
a = a + arguments[i];
}
return a;
}
// 创建缓存代理工厂
const createProxyFactory = function(fn) {
const cache = {};
return function() {
const args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
}
const proxyMult = createProxyFactory(mult),
proxyPlus = createProxyFactory(plus);
proxyMult(1, 2, 3);
proxyPlus(1, 2, 3);
定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以顺利访问其中的每个元素。
// 简单的一个迭代器
class Iterator {
constructor(obj) {
this.current = 0;
this.obj = obj;
}
next() {
this.current += 1;
}
isDone() {
return this.current >= this.obj.length;
}
getCurrItem() {
return this.obj[this.current];
}
}
const iterator = new Iterator([1,2,3,4]);
while (!iterator.isDone()) {
console.log(iterator.getCurrItem())
iterator.next()
}
例子:可以用于某些特殊结构迭代检查,如检测 js 某个功能在浏览器是否兼容性等
const getHuohu = function(){
console.log('检测在火狐中是否可以执行');
return false;
}
const getGuge = function(){
console.log('检测在谷歌中是否可以执行');
return true;
}
const iteratorObj = function(){
for(let i = 0, fn; fn = arguments[i++];){
let result = fn();
if(result !== false){
return result;
}
}
}
iteratorObj(getHuohu, getGuge)
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
发布—订阅模式:
class Event{
constructor(){
this.clientList = {};
}
listen(key, fn){
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
}
trigger(){
const key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if(!fns || fns.length === 0){
return false;
}
for(let i = 0, fn; fn = fns[i++];){
fn.apply(this, arguments);
}
}
remove(key, fn){
const fns = this.clientList[key];
if(!fns){
return false;
}
if(!fn) {
// 订阅类型的事件全部取消
fns && (fns.length = 0);
}else {
for( let l = fns.length - 1; l >= 0; l--){
let _fn = fns[l];
if( _fn === fn){
fns.splice(l, 1);
}
}
}
}
}
let event = new Event();
event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
console.log( '价格= ' + price ); // 输出:'价格=2000000'
});
event.trigger( 'squareMeter88', 2000000 );
定义:在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
简单命令模式:
const closeDoorCommand = {
execute: function() {
// something
}
};
例子:点击按钮之后,必须项某些负责具体行为的对象发送请求,这些对象就是请求的接收者。但是目前并不知道接收者是什么对象,也不知道接收者究竟会做什么。借助命令类的帮助,以便解开按钮和负责具体行为之间的耦合。
命令模式还支持撤销、重做、排队等操作。
定义:在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
例如一个万能遥控器里面添加一个命令的时候,并不关心这个命令是宏命令还是普通子命令。我们只需要确定它是一个命令,并且这个命令拥有可执行的execute方法,那么这个命令就可以被添加进万能遥控器。
class MacroCommand {
constructor() {
this.commandsList = [];
}
add(command) {
this.commandsList.push(command);
}
execute(){
for(let i = 0, command; command = this.commandsList[i++]; ) {
command.execute();
}
}
}
class openAcCommand {
execute() {
console.log('打开空调');
}
add() {
throw new Error('叶对象不能添加子节点');
}
}
class openTvCommand {
execute() {
console.log('打开电视');
}
add() {
throw new Error('叶对象不能添加子节点');
}
}
const macroCommand = new MacroCommand();
macroCommand.add(new openAcCommand());
macroCommand.add(new openTvCommand());
macroCommand .execute();
// 打开空调
// 打开电视
例子:扫描文件夹
class Folder{
constructor(name) {
this.name = name;
this.parent = null;
this.files = [];
}
add(file) {
file.parent = this;
this.files.push(file);
}
scan() {
console.log('开始扫描文件夹:' + this.name);
for( let i = 0, file, files = this.files; file = files[i++];){
file.scan();
}
}
remove() {
if(!this.parent) {
return;
}
for( let files = this.parent.files, l = files.length - 1; l >= 0; l--){
const file = files[l];
if(file == this) {
files.splice(l, 1);
}
}
}
}
class File {
constructor(name) {
this.name = name;
this.parent = null;
}
add(file) {
throw new Error('不能添加在文件下面');
}
scan() {
console.log('开始扫描文件夹:' + this.name);
}
remove() {
if(!this.parent) {
return;
}
for( let files = this.parent.files, l = files.length - 1; l >= 0; l--){
const file = files[l];
if(file == this) {
files.splice(l, 1);
}
}
}
}
const folder = new Folder('学习资料');
const folder1 = new Folder('JavaScript');
const file1 = new File('node.js');
folder1.add( new File('宝可梦'));
folder.add(folder1);
folder.add(file1);
folder1.remove();
folder.scan();
定义:是一种只需使用继承就可以实现的非常简单模式。由两部分结构组成,第一个部分是一个抽象父类,第二个部分是具体实现的子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
抽象类不能被实例化,一定是用来被某些具体类继承。父类规定了子类的方法和执行这些方法的顺序,子类就应该拥有这些方法,并且提供正确的实现。
class Beverage {
// 模板方法
init() {
this.boilWater();
this.brew();
this.pourInCup();
if ( this.customerWantsCondiments() ){ // 如果挂钩返回 true,则需要调料
this.addCondiments();
}
}
boilWater() {
console.log( '把水煮沸' );
}
brew() {
throw new Error( '子类必须重写 brew 方法' );
}
pourInCup() {
throw new Error( '子类必须重写 pourInCup 方法' );
}
addCondiments() {
throw new Error( '子类必须重写 addCondiments 方法' );
}
// 钩子
customerWantsCondiments() {
return true;
}
}
class Coffee extends Beverage {
brew() {
console.log( '用沸水冲泡咖啡' );
}
pourInCup() {
console.log( '把咖啡倒进杯子' );
}
addCondiments() {
console.log( '加糖和牛奶' );
}
customerWantsCondiments() {
return window.confirm( '请问需要调料吗?' );
}
}
let coffee = new Coffee();
coffee.init();
好莱坞原则:允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件(子类放弃了对自己控制权,而是改为父类通知子类)。常用于其他模式和场景:例如发布-订阅模式和回调函数。
定义:是一种用于性能优化的模式。运用共享技术来有效支持大量细粒度的对象。
将对象属性划分为内部状态与外部状态,尽量减少共有对象的数量。
以下情况发生时便可以使用享元模式:
例子一(文件上传):
Document
例子二(对象池):
Document
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。
请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
例子:
// 异步职责链
class Chain {
constructor(fn) {
this.fn = fn;
// 下一个节点
this.successor = null;
}
setNextSuccess(successor) {
return (this.successor = successor);
}
passRequest() {
const ret = this.fn.apply(this, arguments);
if (ret === "nextSuccessor") {
return (
this.successor &&
this.successor.passRequest.apply(this.successor, arguments)
);
}
return ret;
}
// 异步,手动执行
next() {
return (
this.successor &&
this.successor.passRequest.apply(this.successor, arguments)
);
}
}
const f1 = new Chain(function () {
console.log(1);
return "nextSuccessor";
});
const f2 = new Chain(function () {
console.log(2);
setTimeout(() => {
this.next();
}, 3000);
});
const f3 = new Chain(function () {
console.log(3);
});
f1.setNextSuccess(f2).setNextSuccess(f3);
f1.passRequest();
定义:解除对象与对象之间的紧耦合关系。所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
缺点是会增加一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者经常是巨大的。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那么就可以考虑用中介者模式来重构代码。
例子(泡泡堂):
class Player {
constructor(name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = "alive";
}
win() {
console.log(`${this.name} won`);
}
lose() {
console.log(`${this.name} lost`);
}
die() {
this.state = "dead";
playerDirector.ReceiveMessage("playerDead", this);
}
remove() {
this.state = "dead";
playerDirector.ReceiveMessage("removePlayer", this);
}
changeTeam(color) {
playerDirector.ReceiveMessage("changeTeam", this, color);
}
}
const playerDirector = (function () {
const players = {};
const operations = {};
operations.addPlayer = function (player) {
const teamColor = player.teamColor;
players[teamColor] = players[teamColor] || [];
players[teamColor].push(player);
};
operations.removePlayer = function (player) {
const teamColor = player.teamColor;
const teamPlayers = players[teamColor] || [];
for (let i = teamPlayers.length - 1; i >= 0; i--) {
if (teamPlayers[i] === player) {
teamPlayers.splice(i, 1);
}
}
};
operations.changeTeam = function (player, newTeamColor) {
operations.removePlayer(player);
player.teamColor = newTeamColor;
operations.addPlayer(player);
};
operations.playerDead = function (player) {
const teamColor = player.teamColor;
const teamPlayers = players[teamColor];
let all_dead = true;
for (let i = 0, player; (player = teamPlayers[i++]); ) {
if (player.state !== "dead") {
all_dead = false;
break;
}
}
if (all_dead) {
for (let i = 0, player; (player = teamPlayers[i++]); ) {
player.lose();
}
for (let color in players) {
if (color !== teamColor) {
let teamPlayers = players[color];
for (let i = 0, player; (player = teamPlayers[i++]); ) {
player.win();
}
}
}
}
};
const ReceiveMessage = function () {
const message = Array.prototype.shift.call(arguments);
operations[message].apply(this, arguments);
};
return {
ReceiveMessage,
};
})();
const playerFactory = function (name, teamColor) {
const newPlayer = new Player(name, teamColor);
playerDirector.ReceiveMessage("addPlayer", newPlayer);
return newPlayer;
};
const player1 = playerFactory("小白", "red");
const player2 = playerFactory("小红", "red");
const player3 = playerFactory("小明", "red");
const player4 = playerFactory("小花", "red");
const player5 = playerFactory("悟能", "blue");
const player6 = playerFactory("悟空", "blue");
const player7 = playerFactory("八戒", "blue");
const player8 = playerFactory("唐*", "blue");
player1.changeTeam("blue");
player1.die();
player2.die();
player3.die();
player4.die();
定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。
例子:
Function.prototype.after = function (fn) {
var _self = this;
return function () {
console.log(_self, this)
var result = _self.apply(this, arguments);
fn.apply(this, arguments);
return result;
};
};
Function.prototype.before = function (beforefn) {
var __self = this;
return function () {
beforefn.apply(this, arguments);
return __self.apply(this, arguments);
}
}
document.getElementById = document.getElementById.before(function () {
alert(1);
});
document.getElementById('button');
定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。
把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
例子一(开关):
class OffLightState {
constructor(light) {
this.light = light;
}
buttonWasPressed() {
console.log('弱光');
this.light.setState(this.light.weakLightState);
}
}
class WeakLightState {
constructor(light) {
this.light = light;
}
buttonWasPressed() {
console.log('强光');
this.light.setState(this.light.offLightState);
}
}
class Light {
constructor() {
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState (this);
this.currState = null;
}
init() {
const self = this;
this.currState = this.offLightState;
}
setState(newState) {
this.currState = newState;
}
click() {
this.currState.buttonWasPressed();
}
}
const light = new Light();
light.init();
light.click();
定义:又名包装器,作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
例子:
const googleMap = {
show: function() {
console.log('开始渲染谷歌地图');
}
}
const baiduMap = {
display: function() {
console.log('开始渲染百度地图');
}
}
const baiduMpaAdapter = {
show: function() {
return baiduMap.display();
}
}
const renderMap = function(map) {
if(map.show instanceof Function) {
map.show();
}
}
renderMap(googleMap);
renderMap(baiduMpaAdapter)