享元模式
- 是一种用于性能优化的模式;
- 核心是运用共享技术来有效支持大量细粒度的对象,避免对象间拥有相同的内容造成多余的开销;
内部状态和外部状态
- 享元模式要求对象的属性划分为内部状态和外部状态;
- 目标是减少共享对象的数量;
- 内部状态存储于对象内部;
- 内部状态可以被一些对象共享;
- 内部状态独立于具体的场景,通常不会改变;
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享;
- 这样可以吧所有内部状态相同的对象都指定为同一个共享的对象;而外部状态从对象身上剥离并存储在外部;
文件上传例子
var Upload = function( uploadType, fileName, fileSize ){
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom= null;
};
Upload.prototype.init = function( id ){
var that = this;
this.id = id;
this.dom = document.createElement( 'div' );
this.dom.innerHTML = '<span>文件名称:'+ this.fileName +', 文件大小: '
+ this.fileSize +'</span>' + '<button class="delFile">删除</button>';
this.dom.querySelector( '.delFile' ).onclick = function(){
that.delFile();
}
document.body.appendChild( this.dom );
};
Upload.prototype.delFile = function(){
if(this.fileSize < 3000)
return this.dom.parentNode.removeChild( this.dom );
if(window.confirm('确定要删除该文件吗? ' + this.fileName))
return this.dom.parentNode.removeChild( this.dom );
};
var id = 0;
window.startUpload = function( uploadType, files ){ //uploadType 区分是控件还是flash
for(var i = 0, file; file = files[i++];){
var uploadObj = new Upload( uploadType, file.fileName, file.fileSize );
uploadObj.init( id++ ); // 给upload 对象设置一个唯一的id
}
};
startUpload( 'plugin', [{
fileName: '1.txt',
fileSize: 1000
} , {
fileName: '2.html',
fileSize: 3000
}]);
startUpload( 'flash', [{
fileName: '3.txt',
fileSize: 1000
}]);
var Upload = function(uploadType){
this.uploadType = uploadType;
};
Upload.prototype.delFile = function(id){
uploadManager.setExternalState(id, this);
if(this.fileSize < 3000)
return this.dom.parentNode.removeChild(this.dom);
if(window.confirm('确定要删除该文件吗? ' + this.fileName))
return this.dom.parentNode.removeChild(this.dom);
};
//工厂实例化
var UploadFactory = (function(){
var createdFlyWeightObjs = {};
return {
create: function(uploadType){
if(createdFlyWeightObjs[uploadType])
return createdFlyWeightObjs[uploadType];
return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
//管理器:剥离和封装外部状态
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function(id, uploadType, fileName, fileSize){
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = '<span>文件名称:'+ fileName +', 文件大小: '
+ fileSize +'</span>' + '<button class="delFile">删除</button>';
dom.querySelector('.delFile').onclick = function(){
flyWeightObj.delFile( id );
}
document.body.appendChild(dom);
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj;
},
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[id];
for (var i in uploadData)
flyWeightObj[i] = uploadData[i];
}
}
})();
var id = 0;
window.startUpload = function(uploadType, files ){
for(var i = 0, file; file = files[i++]; )
var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
};
startUpload( 'plugin', [{
fileName: '1.txt',
fileSize: 1000
} , {
fileName: '2.html',
fileSize: 3000
}]);
startUpload( 'flash', [{
fileName: '3.txt',
fileSize: 1000
}]);
翻页的例子
var articles = [];
for(var m = 0; m < 12; m++) {
articles.push('这是第'+ m + '条信息')
};
var Flyweight = function () {
var created = [];
function create() {
var dom = document.createElement('div');
document.getElementById('container').appendChild(dom);
created.push(dom);
return dom;
}
return {
getDiv: function () {
if(created.length < 5) {
return create();
} else {
var div = created.shift();
created.push(div);
return div;
}
}
}
}();
var pager = 0, num = 5, len = articles.length;
for(var i = 0; i < 5; i++) {
Flyweight.getDiv().innerHTML = articles[i];
}
document.getElementById('next_page').onclick = function () {
if(articles.length < 5) return;
var n = ++pager * num % len, j = 0;
for(; j < 5; j++) {
if(articles[n + j]) {
Flyweight.getDiv().innerHTML = articles[n + j];
} else if(articles[n + j - len]) {
Flyweight.getDiv().innerHTML = articles[n + j - len];
} else {
Flyweight.getDiv().innerHTML = '';
}
}
}
适用性
- 使用享元模式会多维护一个
factory
对象和一个manager
对象;所有一般以下情况比较适用享元模式:
- 一个程序中使用了大量相似的对象,并造成很大的内存开销;
- 对象的大多数状态都可以变为外部状态,剥离外部状态后可以用相对少的贡献对象取代;
职责链模式
- 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系;将这些对象连城一条链,并沿着这条链传递请求,直到有一个对象处理它为止;
实际开发中的一个例子
- 变量
orderType
:订单的级别,分别为1,2,3;
pay
:是否支付过定金,false
的话统一降到3;
stock
:库存量,1,2级别不受此此限制;
- 初级代码的话会用大量
if-else
来判断分支;这样耦合度很大且不利于以后修改和维护;使用职责链修改
var order500 = function(orderType, pay, stock) {
if(orderType === 1 && pay === true) {
console.log('1级订单,已支付');
} else {
return 'nextSuccessor';
}
};
var order200 = function(orderType, pay, stock) {
if(orderType === 2 && pay === true) {
console.log('2级订单,已支付');
} else {
return 'nextSuccessor';
}
};
var orderNormal = function(orderType, pay, stock) {
if(stock > 0) {
console.log('普通购买');
} else {
console.log('库存不足');
}
};
var Chain = function (fn) {
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccesor = function (successor) {
return this.successor = successor;
};
Chain.prototype.passRequest = function() {
var ret = this.fn.apply(this, arguments);
if(ret === 'nextSuccessor')
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
return ret;
};
var ChainOrder500 = new Chain(order500);
var ChainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
ChainOrder500.setNextSuccesor(ChainOrder200);
ChainOrder200.setNextSuccesor(chainOrderNormal);
ChainOrder500.passRequest(1, true, 1000);
ChainOrder500.passRequest(2, true, 1000);
ChainOrder500.passRequest(3, true, 1000);
ChainOrder500.passRequest(1, false, 0);
异步的职责链
Chain.prototype.next = function() {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
};
//测试
var fn1 = new Chain(function() {
console.log(1);
return 'nextSuccessor';
})
var fn2 = new Chain(function() {
console.log(2);
var self = this;
setTimeout(function() {
self.next();
}, 1000)
});
var fn3 = new Chain(function() {
console.log(3);
});
fn1.setNextSuccesor(fn2).setNextSuccesor(fn3);
fn1.passRequest();
用AOP实现职责链
Function.prototype.after = function(fn) {
var self = this;
return function() {
var ret = self.apply(this, arguments);
if(ret = 'nextSuccessor')
return fn.apply(this, arguments);
}
};
var oredr = order500.after(order200).after(orderNormal);
order(1, true, 1000);
order(2, true, 1000);
order(1, fasle, 1000);
- 这种把函数叠在一起的方法同时也增加了函数的作用域;如果链条太长的话,也会对性能有较大的影响;
另一个例子
//请求模块
var sendData = function (data, dealType, dom) {
....
xhr.onload = function (event) {
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
dealData(xhr.responseText, dealType, dom);
} else {
//...
}
....
}
....
};
//适配响应数据
var dealData(data, dealType, dom) {
....
switch(dealType) {
case 'sug':
....
createSug(data, dom);
case 'invalidate':
....
createInvalidate(data, dom);
}
....
}
//组件模块
var createSug(data, dom) {
....
}
var createInvalidate(data, dom) {
....
}
- 在半成品方案情况下可以设置如此的责任链模式;
- 耦合之间进行单元测试也更加简单;
职责链的优缺点
- 优点
- 解耦了请求发送者和多个接受者之间的关系;只需把请求传递给第一个节点;
- 链中的节点对象可以灵活地拆分重组;
- 可以手动设定起始节点;
- 缺点
- 由于不能确定每个节点都会正常处理;应该在链尾增加一个保底的接受者节点来处理即将离开链尾的请求;
- 从性能考虑,应该避免过长的职责链带来的性能消耗;