Flyweight Pattern,中文可译作享元模式。它的核心是分离对象的:内在属性和外部属性,然后共享内在属性,组装外在属性。看一个汽车的例子:
/*
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);
}
};
很OOP,但是当需要很多Car的实例时,浏览器可能就慢了。 分析Car的属性,前3者终生不变,并且可以大量复制,作为内在属性。改进如下:
/*
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;
}
};
/*
CarFactory singleton.
*/
var CarFactory = (
function () {
var createdCars = {};
return {
createCar:
function (make, model, year) {
if (createdCars[make + '-' + model + '-' + year]) {
return createdCars[make + '-' + model + '-' + year];
}
else {
var car =
new Car(make, model, year);
createdCars[make + '-' + model + '-' + year] = car;
return car;
}
}
};
})();
/*
CarRecordManager singleton.
*/
var CarRecordManager = (
function () {
var carRecordDatabase = {};
return {
addCarRecord:
function (make, model, year, owner, tag, renewDate) {
var car = CarFactory.createCar(make, model, year);
carRecordDatabase[tag] = {
owner: owner,
renewDate: renewDate,
car: car
};
return carRecordDatabase[tag];
},
getCar:
function (tag) {
return carRecordDatabase[tag];
},
transferOwnership:
function (tag, newOwner, newTag, newRenewDate) {
var record =
this.getCar(tag);
record.owner = newOwner;
record.tag = newTag;
record.renewDate = newRenewDate;
},
renewRegistration:
function (tag, newRenewDate) {
this.getCar(tag).renewDate = newRenewDate;
},
isRegistrationCurrent:
function (tag) {
var today =
new Date();
return today.getTime() < Date.parse(
this.getCar(tag).renewDate);
}
};
})();
//
test
(
function () {
var car = CarRecordManager.addCarRecord("test make", "test model", "2011", "Ray", "JB001", "2012-09-29");
var car2 = CarRecordManager.addCarRecord("test make", "test model", "2011", "Tina", "JB002", "2011-08-27");
var car1 = CarRecordManager.getCar("JB001");
console.log(car == car1);
//
true
console.log(car1.car == car2.car);
//
true
console.log(car2.owner);
//
tina
})();
可以看到,即便需要很多的car,有CarFactory的控制,每个make + model + year的组合的Car实例实质上分别只会存在一个。 而对于Car实例的全部操作均通过CarRecordManager来实现,它通吃汽车的内外在全部属性。
基于js的灵活性,将Car类的声明迁移到CarFactory类中,将CarFactory类声明迁移到CarRecordManager中:
/*
CarRecordManager singleton.
*/
var CarRecordManager = (
function () {
var carRecordDatabase = {};
/*
CarFactory singleton.
*/
var CarFactory = (
function () {
var createdCars = {};
/*
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;
}
};
return {
createCar:
function (make, model, year) {
if (createdCars[make + '-' + model + '-' + year]) {
return createdCars[make + '-' + model + '-' + year];
}
else {
var car =
new Car(make, model, year);
createdCars[make + '-' + model + '-' + year] = car;
return car;
}
}
};
})();
return {
addCarRecord:
function (make, model, year, owner, tag, renewDate) {
var car = CarFactory.createCar(make, model, year);
carRecordDatabase[tag] = {
owner: owner,
renewDate: renewDate,
car: car
};
return carRecordDatabase[tag];
},
getCar:
function (tag) {
return carRecordDatabase[tag];
},
transferOwnership:
function (tag, newOwner, newTag, newRenewDate) {
var record =
this.getCar(tag);
record.owner = newOwner;
record.tag = newTag;
record.renewDate = newRenewDate;
},
renewRegistration:
function (tag, newRenewDate) {
this.getCar(tag).renewDate = newRenewDate;
},
isRegistrationCurrent:
function (tag) {
var today =
new Date();
return today.getTime() < Date.parse(
this.getCar(tag).renewDate);
}
};
})();
//
test
(
function () {
var car = CarRecordManager.addCarRecord("test make", "test model", "2011", "Ray", "JB001", "2012-09-29");
var car2 = CarRecordManager.addCarRecord("test make", "test model", "2011", "Tina", "JB002", "2011-08-27");
var car1 = CarRecordManager.getCar("JB001");
console.log(car == car1);
//
true
console.log(car1.car == car2.car);
//
true
console.log(car2.owner);
//
tina
//
var cc = new Car("test make", "test model", "2012"); //error
})();
这时候,还是Flyweight,但是封装性更好了。 Car的用户看不到CarFactory,更看不到Car。即便你如倒数第二行那样声明var car = new Car(...),得到的也唯有error。同时,它完全不影响原有的正常使用。
关于Flyweight,它的内在属性比较好管理,因为实例对象比较少。而外在属性怎么管理? Car的Flyweight实现,是将它们存储在一个内部的类数组对象:carRecordDataBase中。 发散一下,想到没有,一棵树,它利用Composite Pattern也可以完美的存储很多对象实例。 我们用它来实现一下Flyweight。看如下Calendar的例子:
/*
CalendarItem interface.
*/
var CalendarItem =
new Interface('CalendarItem', ['display']);
/*
CalendarYear class, a composite.
*/
var CalendarYear =
function (year, parent) {
this.year = year;
this.element = document.createElement('div');
this.element.style.display = 'none';
parent.appendChild(
this.element);
function isLeapYear(y) {
return (y > 0) && !(y % 4) && ((y % 100) || !(y % 400));
}
this.months = [];
this.numDays = [31, isLeapYear(
this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (
var i = 0, len = 12; i < len; i++) {
this.months[i] =
new CalendarMonth(i,
this.numDays[i],
this.element);
}
};
CalendarYear.prototype = {
display:
function () {
for (
var i = 0, len =
this.months.length; i < len; i++) {
this.months[i].display();
}
this.element.style.display = 'block';
}
};
/*
CalendarMonth class, a composite.
*/
var CalendarMonth =
function (monthNum, numDays, parent) {
this.monthNum = monthNum;
this.element = document.createElement('div');
this.element.style.display = 'none';
parent.appendChild(
this.element);
this.days = [];
for (
var i = 0, len = numDays; i < len; i++) {
this.days[i] =
new CalendarDay(i,
this.element);
}
};
CalendarMonth.prototype = {
display:
function () {
for (
var i = 0, len =
this.days.length; i < len; i++) {
this.days[i].display();
}
this.element.style.display = 'block';
}
};
/*
CalendarDay class, a leaf.
*/
var CalendarDay =
function (date, parent) {
this.date = date;
this.element = document.createElement('div');
this.element.style.display = 'none';
parent.appendChild(
this.element);
};
CalendarDay.prototype = {
display:
function () {
this.element.style.display = 'block';
this.element.innerHTML =
this.date;
}
};
CalendarYear、CalendarMonth、CalendarDay均实现了CalendarItem接口,CalendarMonth中引用和组装CalendarDay,CalendarYear引用和组装CalendarMonth。初看起来很不多,但是想一想一年得有365天,我连续加载个10年,页面估计就得挂了。
利用Singleton Pattern将CalendarDay一步到位改进如下:
/*
CalendarDay class, a leaf.
*/
var CalendarDay = (
function () {
var innerDay =
null;
var InnerCalendarDay =
function () { };
InnerCalendarDay.prototype = {
display:
function (date, parent) {
var element = document.createElement('div');
element.innerHTML = date;
element.style.display = "block";
parent.appendChild(element);
}
};
return {
getInstance:
function () {
if (innerDay ==
null) {
innerDay =
new InnerCalendarDay();
}
return innerDay;
}
};
})();
可以看到,我们将它的date和parent作为外在属性,在调用display方法时通过参数传入。这时候CalendarDay内部实例仅有1份。基于这个变化,修改CalendarMonth如下:
/*
CalendarMonth class, a composite.
*/
var CalendarMonth =
function (monthNum, numDays, parent) {
this.monthNum = monthNum;
this.element = document.createElement('div');
this.element.style.display = 'none';
parent.appendChild(
this.element);
this.days = [];
for (
var i = 0, len = numDays; i < len; i++) {
this
.days[i] = CalendarDay.getInstance();
}
};
CalendarMonth.prototype = {
display:
function () {
for (
var i = 0, len =
this.days.length; i < len; i++) {
this
.days[i].display(i,
this
.element);
}
this.element.style.display = 'block';
}
};
至此,CalendarDay级别已经优化。 你可以参考这个思路继续。 另外,创建的那些DOM元素其实也可以根据需要来进行共享。 这里暂不进行。
这里再列举Tooltip的例子。一般性的代码:
var Tooltip =
function(targetElement, text) {
this.target = targetElement;
this.text = text;
this.delayTimeout =
null;
this.delay = 1500;
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;
var that =
this;
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';
}
};
//
test
var linkElement = $('link-id');
var tt =
new Tooltip(linkElement, 'Lorem ipsum...');
同样可能会导致N对类似对象被大量创建。改进如下:
/*
TooltipManager singleton, a flyweight factory and manager.
*/
var TooltipManager = (
function () {
var storedInstance =
null;
/*
Tooltip class, as a flyweight.
*/
var Tooltip =
function () {
this.delayTimeout =
null;
this.delay = 1500;
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';
}
};
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;
}
};
})();
/*
Tooltip usage.
*/
TooltipManager.addTooltip($('link-id'), 'Lorem ipsum...');
全部源码download