小议javascript设计模式(3)
作者:oldfish 来源:alipay UED 时间:2009-10-09 13:31:00
Javascript设计模式之工厂模式Factory
工厂模式Factory:先创建一个抽象类,然后基于这个抽象类派生出子类,并在子类中创建工厂方法,从而把实例化推迟到对应的子类中进行,说实话,工厂模式在javascript中的应用有些牵强,毕竟javascript不像java存在硬编码带来的困搅,要学习的只是模式的思想,切忌因为模式而模式。
不妨举个偏激点的例子,为tab切换、下拉列表等组件添加定位,渐隐,延迟等效果,我们可以先为这些组件定义一个接口:
var Iwidget = new Interface("iwidget",[["addEffect"]]);
定义该接口,以便之后派生的子类继承,接口中定义了一个addEffect方法,接口方法实现后,调用的同学大可不必关注各子类中对于addEffect方法的代码实现。
var Widget = function(){};
Widget.prototype={
fire:function(model){
var widget = this.createWidget(model);
//有同学问为什么子类都必须定义接口方法,因为下面要调用嘛
widget.addEffect();
return widget;
},
show:function(){
//show代码具体实现
},
hide:function(){
//hide代码具体实现
},
createWidget:function(model){
alert('抽象类,不可以实例化')
}
};
上例先定义一个抽象类Widget,做为派生子类的父类,由于考虑到这两类组件都涉及到隐藏和显示一个容器,所以在父类中预先定义好show和hide方法以便子类继承。
var xTab = function(){};
extend(xTab,Widget);
xTab.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new xTabPosition();
break;
case 'anim':
widget = new xTabAnim();
break;
case 'delay':
default:
widget = new xTabDelay();
}
};
var dropDown = function(){};
extend(dropDown,Widget);
dropDown.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new dropDownPosition();
break;
case 'anim':
widget = new dropDownAnim();
break;
case 'delay':
default:
widget = new dropDownDelay();
}
};
子类xTab和dropDown继承了父类,并且重写了createWidget方法,不同的子类根据定位,渐隐,延迟效果分别创建不同的实例,只要创建这些实例的类都实现接口中约定的addEffect方法,至于方法代码如何实现,千篇一律,爱咋整咋整。
var xTabPosition = function(){};
xTabPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownPosition = function(){};
dropDownPosition.prototype ={
addEffect:function(){
//具体实现代码
}
};
var dropDownInstance = new dropDown();
dropDownInstance.fire('position');
以此类推,如果您需要为气泡组件添加这些效果,照葫芦画瓢就可以了,说到这里你可以清楚的看到,这种设计模式大大降低了类和类之间的耦合度,而且可以根据具体的交互需求,实现不同的辅助动作,但是也无可避免的增加了代码实现上的复杂性,事实上这种模式并不适合javascript,毕竟它有别于java,不会有类名硬编码的问题,目的是学习他的设计思想,所以以上示例仅供参考,如无大人在旁,小朋友切勿效仿。
对于javascript爱好者来说,更有价值的应该是工厂模式中讲到的的”缓存(memoization)机制”,书上举了个创建XHR对象的例子来说明该特性,但是效果显然不够明显……
memoization名词解释:把函数的每次执行结果都放入一个键值对(数组也可以,视情况而定)中,在接下来的执行中,在键值对中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才 真正执行函数体的求值部分。很明显,找值,尤其是在键值对中找值,比执行函数快多了
在递归调用的时候,memoization的威力才能更好的显现。下面是一个经典的斐波纳契序列,fib(20) 会把fib这个方法执行21891次,如果是fib(40),这会执行331160281次。
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
再看看如何使用memoization来实现:
var iterMemoFib = (function() {
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
//将一个递归转换成了一个
for (var i = cache.length; i <= n; i++) {
cache[i] = cache[i - 2] + cache[i - 1];
}
}
return cache[n-1];
}
return fib;
})();
将Function的原型扩展memoize 和unmemoize 方法,这样你可以对任何函数实现memoize和解除memoize,当然,这个方法要慎,对一些不是频繁执行的函数,没必要缓存:
Function.prototype.memoize = function() {
var pad = {};
var self = this;
var obj = arguments.length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// 把参数作为数组保存,作为键,把函数执行的结果作为值缓存起来
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
Function.prototype.unmemoize = function() {
alert("Attempt to unmemoize an unmemoized function.");
return null;
}
使用方法:fib.memoize();