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; } };
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; } } }; })();
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); } }; })();
从这个例子中,我们可以看到,为了提高性能,程序增加了复杂性。原来只有一个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'; } };
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; } }; })();
通过flyweight pattern,我们用一个浮动框就解决问题了,大大的提高的性能。