TIP
设计模式在javascript中有自己的展现特点
工厂模式
简单工厂
假设你想开几个自行车商店,每个店都有几种型号的自行车出售。这里用一个类来表示。
class BicycleShop {
sellBicycle(model) {
let bicycle;
switch(model) {
case 'The Speedster':
bicycle = new Speedster();
break;
case 'The Lowrider':
bicycle = new Lowrider();
break;
case 'The Comfort Cruiser':
default:
bicycle new ComfortCruiser();
}
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}
// 一种型号的自行车
class Speedster {
assemle(){
...
}
wash() {
...
}
ride() {
...
}
repair() {
...
}
}
要出售某种型号的自行车,只需要调用sellBicycle方法就可以
const shop1 = new BicycleShop();
const bicycle = shop1.sellBicycle('The Speedster');
但是当你要加入一款新型车时,你必须去修改BicycleShop这个类,尽管这个类的功能并没有发生改变。我们把sellBicycle方法中创建实例的工作交给一个简单工厂
// 对象
const BicycleFactory = {
createBicycle(model) {
let bicycle;
switch(model) {
case 'The Speedster':
bicycle = new Speedster();
break;
case 'The Lowrider':
bicycle = new Lowrider();
break;
case 'The Flatlander':
bicycle = new Flatlander();
break;
case 'The Comfort Cruiser':
default:
bicycle = new ComfortCruiser();
}
return bicycle;
}
}
// 类
class BicycleFactory {
createBicycle(model) {
let bicycle;
switch(model) {
case 'The Speedster':
bicycle = new Speedster();
break;
case 'The Lowrider':
bicycle = new Lowrider();
break;
case 'The Flatlander':
bicycle = new Flatlander();
break;
case 'The Comfort Cruiser':
default:
bicycle = new ComfortCruiser();
}
return bicycle;
}
}
class BicycleShop {
sellBicycle(mode) {
const bicycle = BicycleFactory.createBicycle(mode);
// const bicycle = new BicycleFactory().createBicycle(mode);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}
这种模式把成员对象的创建工作交给一个外部对象。这个外部对象可以是一个简单的命名空间,也可以是一个类的实例。如果负责创建实例的方法的逻辑不会发生变化,那么一般来说用单体或静态类方法创建这些成员实例。但如果你要提供几种不同品牌的自行车,那么更恰当的做法是把这个创建方法实现在一个类中,并从该类派生出一些子类。
工厂模式
真正的工厂模式与简单工厂模式的区别在于,它不是另外使用一个类或对象来创建自行车,而是使用一个子类。按照正式定义,工厂是一个将成员对象的实例化推迟到子类中进行的类。
我们打算让各个自行车商店自行决定从那个生产厂家进货。我们可以吧BicycleShop设计为抽象类,让子类根据各自的进货渠道实现自己的craeteBicycle方法。
class BicycleShop {
sellBicycle(mode) {
const bicycle = this.createBicycle(mode);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
craeteBicycle(model) {
throw new Error('Unsupported operation on an abstract class');
}
}
这个类中定义了createBicycle方法,但真要调用这个方法的话会报错,只能用子类来实现这个方法
例子,其中一个子类代表的商店从Acme公司进货,而另一个则从General Products公司进货
class AcmeBicycleShop extends BicycleShop {
createBicycle(model) {
let bicycle;
switch(model) {
case 'The Speedster':
bicycle = new AcmeSpeedster();
break;
case 'The Lowrider':
bicycle = new AcmeLowrider();
break;
case 'The Flatlander':
bicycle = new AcmeFlatlander();
break;
case 'The Comfort Cruiser':
default:
bicycle = new AcmeComfortCruiser();
}
return bicycle;
}
}
class GeneralProductsBicycleShop extends BicycleShop {
createBicycle(model) {
let bicycle;
switch(model) {
case 'The Speedster':
bicycle = new GeneralProductsSpeedster();
break;
case 'The Lowrider':
bicycle = new GeneralProductsLowrider();
break;
case 'The Flatlander':
bicycle = new GeneralProductsFlatlander();
break;
case 'The Comfort Cruiser':
default:
bicycle = new GeneralProductsComfortCruiser();
}
return bicycle;
}
}
这些工厂方法生成的对象都实现了Bicycle接口,所以再其他代码眼里它们完全可以互换。自行车的销售工作还是和以前意一样,只是现在所开的商店可以是Acme或General Products的专卖店
const shop1 = new AcmeBicycleShop();
const bike1 = shop1.sellBicycle('the Lowrider');
const shop2 = new GeneralProductsBicycleShop();
const bike2 = shop2.sellBicycle('the Lowrider');
因为两个生产厂家生产的自行车款式完全相同,所以顾客买车是可以不用关心车到底是哪家生产的。 增加对其他生产厂家的支持横简单,只需要再创建一个BicycleShop的子类并重新定义createBicycle工厂方法即可。我们也可以对各个子类进行修改,以支持相关厂家其他型号的产品。这是工厂模式的重要特点。对Bicycle进行一般性操作的代码可以全部写在父类BicycleShop中,二队具体的Bicycle对象进行实例化的工作则被留到子类中。一般性的代码集中在一个位置,而个体性的代码则被封装在子类中。
单例模式
TIP
保证一个类仅有一个实例,并提供一个访问它的全局访问点
var Singleton = function(name) {
this.name = name;
this.instance = null;
}
Singleton.getInstance = function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a===b); // true
console.log(a.name); // a
console.log(b.name); // a
// 闭包实现
var Singleton = function(name) {
this.name = name;
}
Singleton.getInstance = (function() {
let instance = null;
return function() {
if(!instance) {
instance = new Singleton(name);
}
return instance;
};
})()
透明的单例模式
TIP
像使用其它普通类时一样使用
var Singleton = (function(){
let instance = null;
var Singleton = function(name) {
if(instance) return instance;
this.name = name;
return instance = this;
}
return Singleton;
})();
var a = new Singleton('a');
var b = new Singleton('b');
console.log(a===b); // true
console.log(a.name); // a
console.log(b.name); // a
用代理实现单例模式
TIP
同一个类,需要同时使用单例模式和非单例模式
var ShowName = function(name) {
this.name = name;
}
ShowName.prototype.log = function() {
console.log(this.name);
}
var ProxySingleton = (function(){
var instance;
return function(name) {
if(!instance) {
instance = new ShowName(name);
}
return instance;
}
})();
var a = new ProxySingleton('a');
var b = new ProxySingleton('b');
console.log(a===b); // true
console.log(a.name); // a
console.log(b.name); // a
惰性单例
TIP
在需要的时候才创建对象实例
之前的getInstance就是惰性单例
通用惰性单例
js// 传入创建对象的方法 function getSingle(fn) { var result; return function() { return result || fn.apply(this,arguments); } }
策略模式
TIP
定义一些列的算法,把它们一个个封装起来,并且使它们可以相互替换
// 计算奖金
var strategies = {
'S': function(salary) {
return salary * 4;
},
'A': function(salary) {
return salary * 3;
},
'B': function(salary) {
return salary * 2;
}
}
var calculateBonus = function(level, salary) {
return strategies[level](salary);
}
代理模式
TIP
为一个对象提供一个代用品或占位符,以便控制对它的访问
明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。
// 小明通过B送花给A
var Flower = function(){};
var xiaoming = {
sendFlower: function(target) {
var flower = new Flower();
target.receiveFlower(flower);
}
};
var B = {
receiveFlower: function(flower) {
A.listenGoodMood(function(){
// 监听A的好心情
A.receiveFlower(flower);
})
}
};
var A = {
receiveFlower: function(flower) {
console.log('收到花 ' + flower);
},
listenGoodMood: function(fn) {
setTimeout(function(){
// 假设10秒之后A的心情变好
fn();
}, 10000);
}
};
xiaoming.sendFlower(B);
保护代理
代理B可以帮助A过滤掉一些请求,这种代理叫作保护代理。
虚拟代理
把一些开销很大的对象延迟到真正需要它的时候才去创建
jsvar B = { receiveFlower: function(flower) { A.listenGoodMood(function(){ var flower = new Flower(); A.recelveFlower(flower); }) } }
js// 虚拟代理实现图片加载 // 真正图片加载号之前,先把img节点的src设置为一张loading图片 var myImage = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } } })(); var proxyImage = (function(){ var img = new Image; img.onload = function() { myImage.setSrc(this.src); } return { setSrc: function(src) { myImage.setSrc('file:// /D://loading.gif'); img.src = src; } } })(); proxyImage.setSrc('http:// www.xxx.com/test.jpeg');
TIP
在编写业务代码的时候,不需要预先猜测是否需要代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟
迭代器模式
TIP
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
内部迭代器 foreach、$.each、for in绝大部分语言都内置了迭代器
外部迭代器
必须显示地请求迭代下一个元素 next
发布-订阅模式(观察者模式)
TIP
定义对象间的一种一对多得依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffice.listen = function(fn) { // 增加订阅者
this.clientList.push(fn); // 订阅的消息添加进缓存列表
}
salesOffices.trigger = function() { // 发布消息
for(var i=0,fn;fn=this.clientList[i++];){
fn.apply(this, arguments); //arguments是发布消息时带上的参数
}
}
// 测试
// 小明订阅消息
salesOffices.listen(function(price, squareMeter) {
console.log('价格=' + price);
console.log('squareMeter= ' + squareMeter);
});
// 小红订阅消息
salesOffices.listen(function(price, squareMeter) {
console.log('价格=' + price);
console.log('squareMeter= ' + squareMeter);
})
salesOffices.trigger(2000000, 88); // 价格=2000000 squareMeter=88
salesOffices.trigger(3000000, 110); // 价格=2000000 squareMeter=110
小明只想买88平米的房子,但是110平米的消息也推送给了他
// 改进,订阅者只收到自己感兴趣的消息
var salesOffices = {};
salesOffices.clientList = {};
salesOffices.listen = function(key, fn) {
if(!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
}
salesOffices.trigger = function() {
var key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if(!fns || fns.length === 0) {
return false;
}
for(var i=0,fn; fn=fns[i++]) {
fn.apply(this, arguments);
}
}
通用实现
var event = {
clientList: {},
listen: function(key, fn) {
if(!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function() {
var key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if(!fns || fns.length===0) {
return false;
}
for(var i=0,fn; fn=fns[i++]) {
fn.apply(this, arguments);
}
},
remove: function(key, fn) {
var fns = this.clientList[key];
if(!fns) {
return false;
}
if(!fn) { // 如果没有传入具体的回调函数,表示需要取消key的对象消息的所有订阅
fns&&(fns.length=0);
} else {
for(var l = fns.length-1; l>=0; l--) {
var _fn = fns[l];
if(_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
}
}
// 给对象动态安装发布订阅功能
var installEvent = function(obj) {
for(var i in event) {
obj[i] = event[i];
}
}
命令模式
TIP
命令模式中的命令(command)指的是一个执行某些特定事情的指令
有时候需要相某些对象发送请求,但是并不知道请求的接收者是谁?也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者好请求接收者能够消除彼此之间的耦合关系。
命令模式还支持撤销、排队等操作。
命令模式的由来,其实是回调函数的一个面向对象的替代品。
<button id="button1">1</button>
<button id="button2">2</button>
<button id="button3">3</button>
<script>
var btn1 = document.getElementById('button1');
var btn2 = document.getElementById('button2');
var btn3 = document.getElementById('button3');
</script>
var setCommand = function(button, command) {
button.onclick = function() {
command.execute();
}
}
var MenuBar = {
refresh: function() {
console.log('刷新菜单目录');
}
};
var subMenu = {
add: function() {
console.log('增加子菜单');
},
del: function() {
console.log('删除子菜单');
}
}
var RefreshMenuBarCommand = function(receiver) {
this.receiver = receiver;
};
RefreshMenuBarCommand.prototype.execute = function() {
this.receiver.refresh();
}
var AddSubMenuCommand = function(receiver) {
this.receiver = receiver;
};
AddSubMenuCommand.prototype.execute = function() {
this.receiver.add();
}
var DelSubMenuCommand = function(receiver) {
this.receiver = receiver;
};
DelSubMenuCommand.prototype.execute = function() {
this.receiver.del();
}
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(MenuBar);
var delSubMenuCommand = new DelSubMenuCommand(MenuBar);
setCommand(btn1, refreshMenuBarCommand);
setCommand(btn2, addSubMenuCommand);
setCommand(btn3, delSubMenuCommand);
组合模式
TIP
用小的子对象来构建更大的对象,而这些小的子对象本身也许由更小的‘孙对象’构成的。
var closeDoorCommand = {
execute: function() {
console.log('关门');
}
};
var openPcCommand = {
execute: function() {
console.log('开电脑');
}
};
var openQQCommand = {
execute: function() {
console.log('登录QQ');
}
};
var MacroCommand = function() {
return {
commandsList: [],
add: function(command) {
this.canmandsList.push(command);
},
execute: function() {
for(var i=0,command;command=this.commandsList[i++]) {
command.execute();
}
}
};
};
var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();
其中,marcoCommand被称为组合对象
组合模式将对象组合成树形结构,以表示‘部分-整体’的层次结构。除了用来表示树形结构外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
组合模式除了要求组合对象和叶对象拥有相同的接口之外,还要求对一组叶对象的操作必须具有一致性。
何时使用组合模式
- 表示对象的部分-整体层次结构
- 呵护希望统一对待树中的所有对象。
模板方法模式
TIP
是一种只需要使用继承就可以实现的非常简单的模式。 由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常抽象在抽象父类中封装了子类的算法框架,包括实现一些公共方法一级封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类方法。