why?
当一大堆类似的复杂对象被创建,导致内存被大量占用,浏览性能大大降低时,我们应该有所行动,需要优化这些对象。Flyweight Pattern 就是用来做大量对象优化工作的。
how ?
Flyweight Pattern 将大量的这些复杂对象,转换为少许的一些共享对象,这将大大减少内存的占有
what?
Flyweight Pattern 目标是减少你需要创造的对象数目。这是通过将对象的内部状态分拆为两个部分:intrinsic data 和 extrinsic data。intrinsic data 是对象类的方法需要用到的数据,没有这些数据,类将不能完成功能。extrinsic data 是可以从类中移除的,并保存在外面。
这样,我们可以将拥有相同intrinsic data 的对象用一个共享对象代替,最后,一大推的类似对象,将变为少许几个拥有不同intrinsic data 的对象
需要factory:没有简单的通过构造函数创造对象,通过factory 创造这些共享对象,因为这样,我们可以跟踪记录有哪些对象被创建,并且只创造有不同intrinsic data 的对象。
需要manager:manager 对象用来存储对象的extrinsic data。每当涉及到一个对象的方法被调用时,manager 需要传递extrinsic data 过去作为参数
案例分析:
1 car registration
现在需要登记城市里面的所有车的情况,你需要存储的内容有车的生产厂家、型号、出产日期,还有车的拥有关系,如车主姓名、车牌号、登记日期)。
刚开始,我们用一个对象表示一辆车:
/* Car class, un-optimized. */
var Car = function(make, model, year, owner, tag, renewDate) {
this.make = make;
this.model = model;
this.year = year;
this.owner = owner;
this.tag = tag;
this.renewDate = renewDate;
};
Car.prototype = {
getMake: function() {
return this.make;
},
getModel: function() {
return this.model;
},
getYear: function() {
return this.year;
},
transferOwnership: function(newOwner, newTag, newRenewDate) {
this.owner = newOwner;
this.tag = newTag;
this.renewDate = newRenewDate;
},
renewRegistration: function(newRenewDate) {
this.renewDate = newRenewDate;
},
isRegistrationCurrent: function() {
var today = new Date();
return today.getTime() < Date.parse(this.renewDate);
}
};
如果城市有几百万量车,你也将创造几百万个对象,这将导致严重的内存问题。so,flyweight pattern 出现了,通过他来优化这个结构
首先分离intrinsic data 和 extrinsic data
分离这些数据,有点随意。主要依据原则是:尽可能多的把数据放在extrinsic data中,同时还保留对象的完整性。
这个例子中,我们将车的生产厂家、型号、出产日期作为intrinsic data ,车主姓名、车牌号、登记日期作为extrinsic data。这意味着,拥有相同生产厂家、型号、出产日期的车,将共享一个对象。虽然生产厂家、型号、出产日期的不同组合,还是可以参数很多对象,但是比起为每一个车产生一个对象,少得很多。
/* Car class, optimized as a flyweight. */
var Car = function(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
};
Car.prototype = {
getMake: function() {
return this.make;
},
getModel: function() {
return this.model;
},
getYear: function() {
return this.year;
}
};
同之前的car 类相比,这个类,把所有的extrinsic data 移除了。所有与注册相关的方法都移到manager object 中去了(虽然可以依然留下这些方法,并通过参数传递的方式,将信息传递进去)。
2 通过factory 来实例化car 对象
不能通过简单的factory 函数来生成对象,因为函数内部无法对历史生成对象进行保存管理,so,需要用一个singleton来达到保存的目的
/* CarFactory singleton. */
var CarFactory = (function() {
var createdCars = {};
return {
createCar: function(make, model, year) {
// Check to see if this particular combination has been created before.
if(createdCars[make + '-' + model + '-' + year]) {
return createdCars[make + '-' + model + '-' + year];
}
// Otherwise create a new instance and save it.
else {
var car = new Car(make, model, year);
createdCars[make + '-' + model + '-' + year] = car;
return car;
}
}
};
})();
createdCars 中保存了已经生成的对象,在factory 对象中,如果以前没有生成对应的对象,则,生成;如果以前生成过,则用以前的就行。
3 完成优化,还需要最后一个对象--manager 对象
需要manager 对象保存extrinsic data 。这样原来一个car 对象的数据分成了两部分,拥有相同instrinsic data 的共享对象 以及 extrinsic data。这两部分在manager 对象中结合。
/* CarRecordManager singleton. */
var CarRecordManager = (function() {
var carRecordDatabase = {};
return {
// Add a new car record into the city's system.
addCarRecord: function(make, model, year, owner, tag, renewDate) {
var car = CarFactory.createCar(make, model, year);
carRecordDatabase[tag] = {
owner: owner,
renewDate: renewDate,
car: car
};
},
// Methods previously contained in the Car class.
transferOwnership: function(tag, newOwner, newTag, newRenewDate) {
var record = carRecordDatabase[tag];
record.owner = newOwner;
record.tag = newTag;
record.renewDate = newRenewDate;
},
renewRegistration: function(tag, newRenewDate) {
carRecordDatabase[tag].renewDate = newRenewDate;
},
isRegistrationCurrent: function(tag) {
var today = new Date();
return today.getTime() < Date.parse(carRecordDatabase[tag].renewDate);
}
};
})();
最后,所有的车辆数据都保存在manager 对象的私有变量:carRecordDatabase 中。所有与extrinsic data 有关的操作都封装在manager 中。
从这个例子中,我们可以看到,为了提高性能,程序增加了复杂性。原来只有一个class,现在有一个class,还有两个singleton 对象;原来的数据放在一个地方,现在数据放在了两个地方。但是对于提高性能,这个还是值得的
如果管理extrinsic data,这里介绍了一种,即,在manager 中,统一管理共享对象和extrinsic data。另一个管理方式是通过composite pattern 。利用树状继承关系来保存extrinsic data。叶子节点是在继承中共享的对象
案例2:tooltip object
鼠标移动到某个按钮上时,弹出浮动框,提示按钮的作用。
/* Tooltip class, un-optimized. */
var Tooltip = function(targetElement, text) {
this.target = targetElement;
this.text = text;
this.delayTimeout = null;
this.delay = 1500; // in milliseconds.
// Create the HTML.
this.element = document.createElement('div');
this.element.style.display = 'none';
this.element.style.position = 'absolute';
this.element.className = 'tooltip';
document.getElementsByTagName('body')[0].appendChild(this.element);
this.element.innerHTML = this.text;
// Attach the events.
var that = this; // Correcting the scope.
addEvent(this.target, 'mouseover', function(e) { that.startDelay(e); });
addEvent(this.target, 'mouseout', function(e) { that.hide(); });
};
Tooltip.prototype = {
startDelay: function(e) {
if(this.delayTimeout == null) {
var that = this;
var x = e.clientX;
var y = e.clientY;
this.delayTimeout = setTimeout(function() {
that.show(x, y);
}, this.delay);
}
},
show: function(x, y) {
clearTimeout(this.delayTimeout);
this.delayTimeout = null;
this.element.style.left = x + 'px';
this.element.style.top = (y + 20) + 'px';
this.element.style.display = 'block';
},
hide: function() {
clearTimeout(this.delayTimeout);
this.delayTimeout = null;
this.element.style.display = 'none';
}
};
如果页面上有100个按钮,我们就要生成100个浮动框。太多的dom ,将严重影响性能。其实浮动框的样式都一样,仅仅是内容不一样而已
利用flyweight pattern,移除extrinsic data ,浮动框类将如下:
/* Tooltip class, as a flyweight. */
var Tooltip = function() {
this.delayTimeout = null;
this.delay = 1500; // in milliseconds.
// Create the HTML.
this.element = document.createElement('div');
this.element.style.display = 'none';
this.element.style.position = 'absolute';
this.element.className = 'tooltip';
document.getElementsByTagName('body')[0].appendChild(this.element);
};
Tooltip.prototype = {
startDelay: function(e, text) {
if(this.delayTimeout == null) {
var that = this;
var x = e.clientX;
var y = e.clientY;
this.delayTimeout = setTimeout(function() {
that.show(x, y, text);
}, this.delay);
}
},
show: function(x, y, text) {
clearTimeout(this.delayTimeout);
this.delayTimeout = null;
this.element.innerHTML = text;
this.element.style.left = x + 'px';
this.element.style.top = (y + 20) + 'px';
this.element.style.display = 'block';
},
hide: function() {
clearTimeout(this.delayTimeout);
this.delayTimeout = null;
this.element.style.display = 'none';
}
};
这个例子中,与extinsic data 有关的方法,并没有移动到manager中,这些方法通过参数得到extrinsic data
Tooltip 的声明将放在TooltipManager 中,这样,他将不会再别的地方实例化
/* TooltipManager singleton, a flyweight factory and manager. */
var TooltipManager = (function() {
var storedInstance = null;
/* Tooltip class, as a flyweight. */
var Tooltip = function() {
...
};
Tooltip.prototype = {
...
};
return {
addTooltip: function(targetElement, text) {
// Get the tooltip object.
var tt = this.getTooltip();
// Attach the events.
addEvent(targetElement, 'mouseover', function(e) { tt.startDelay(e, text); });
addEvent(targetElement, 'mouseout', function(e) { tt.hide(); });
},
getTooltip: function() {
if(storedInstance == null) {
storedInstance = new Tooltip();
}
return storedInstance;
}
};
})();
getTooltip 即产生共享对象的factory 方法。
通过flyweight pattern,我们用一个浮动框就解决问题了,大大的提高的性能。