组合模式是一种专为创建Web上动态用户界面而量身定制的模式。使用这种模式,可以用一条命令在多个对象上激发复杂的或递归的行为。
组合模式为操劳过度的javascript程序员带来了两大的好处
你可以用同样的方法处理对象的集合与其中的特定子对象。组合对象与组成它的对象实现了同一批操作。对组合对象执行的这些操作将向下传递到所有的组成对象,这样一来所有的组成对象都会执行同样的操作。在存在大批对象的情况下,这是一种非常有效的技术。
它可以用来把一批子对象组织成树形结构,并且使整棵树都可被遍历。所有组合对象都实现了一个用来获取其子对象的方法。借助这个方法,你可以隐藏实现的细节并随心所欲组织子对象。任何使用这个对象的代码都不会对其内部实现形成依赖。
在组合对象的层次体系中有两种类型的对象:叶对象和组合对象。这是一个递归定义,但这正是组合模式如此有用的原因所在。一个组合对象由一些别的组合 对象和叶对象组成。其中只有叶对象不再包含子对象。叶对象是组合对象中最基本的元素,也是各种操作的落实地点。
只有同时具备如下两个条件时才适合使用组合模式:
1. 存在一批组织成某种层次体系的对象(具体的结构在开发期间可能无法得知)
2. 希望对这批对象或其中的一部分对象实施一个操作。
组合模式擅长于对大批对象进行操作。它专为组织这类对象并把操作从一个层次向下一层次传递而设计。
假设你茹柔吐刚个新项目。乍一看任务很简单:创建一个表单,要求可以保存、恢复和验证其中的值。随便一个半吊子Web开发人员都能搞定,不是吗?问题在于,这个表单元素中的内容和数目都是完全未知的,而且会因用户而异。典型的例子就是,那种紧密耦合到name和address这类特定的表单域的validate函数不会管用,因为在开发期间无法得知要验证哪些域。这是因为开发期间无法得知验证哪些域。这正是组合模式可以大显身手的地方。
首先,我们应该逐一鉴别表单的各个组成元素,判断它属于组合对象还是叶对象。表单最基本的构成要素是用户用以输入数据的域,它们由input、select和textarea标签生成。用于组织相关域的fieldset标签属于上面的一个层次。位于最顶屋的是表单自身。
首先要做的是创建那些组合对象和叶对象需要实现两个接口:
var Composite =new Interface(“Composite”,[“add”,”remove”,”getChild”]);
var FormItem = new Interface(“FormItem”,[“save”]);
CompositeForm类的代码如下:
var CompositeForm= function(id,method,action){
this.formComponents = [];
this.element = document.createElement(‘form’);
this.element.id = id;
this.element.method = method || ‘POST’;
this.element.action = action || “#”;
}
CompositeForm.prototype.add = function(child){
Interface.ensureImplements(child,Composite,FormItem);
this.formComponents.push(child);
this.element.appendChild(child.getElement());
}
CompositeForm.prototype.remove = function(child){
for(vari=0,len=this.formComponents.length;i if(this.formComponents[i]===child){ this.formComponents.splice(i,1); break; } } } CompositeForm.prototype.getChild = function(child){ return this.formComponents[i]; } CompositeForm.prototype.save = function(child){ for(vari=0,len=this.formComponents.length;i if(this.formComponents[i]===child){ this.formComponents.save(); } } } CompositeForm.prototype.getElement = function(child){ return this.element; } 叶对象类 var Field =function(id){ this.id = id; this.element; } Field.prototype.add= function(){}; Field.prototype.remove= function(){}; Field.prototype.getChild= function(){}; Field.prototype.save= function(){ setCookie(this.id,this.getValue()); }; Field.prototype.getElement= function(){ return this.element; }; Field.prototype.getValue= function(){ throw new Error(‘Unsupported operation onthe class Field.’); }; 这个类将被各个叶对象类继承。它将Composite接口中的方法实现了空函数,这是因为叶节点不会有子对象。你也可以考虑让这几个函数异常。 save方法用getValue方法获得所要保存的对象值,后一方法在各个子类中的实现各不相同。使用save方法,不用提交表单也能保存表单的内容。对于这种长长的表单来说这尤其有用,因为用户可以把数据保存下来,等以后再回来完成表单的填写。 var InputField =function(id,label){ Field.call(this,id); this.input = document.create(‘input’); this.input.id = id; this.label = document.create(‘label’); var labelTextNode = document.createTextNode(label); this.element = document.createElement(‘div’); this.element.className = “input-field”; this.element.appendChild(this.label); this.element.appendChild(this.input); } extend(InputField,Field);//Inherit from Field. InputField.prototype.getValue=function(){ return this.input.value; } InputField是Field的子类之一。它的大多数方法都是从Field继承而来,但它也实现了针对input标签的getValue方法的代码。TextareaField和SelectField也实现了自己特有的getValue方法: var TextareaField= function(id,label){ //implements composite, formitem Field.call(this,id); this.textarea = document.createElement(‘textarea’); this.textarea.id = id; this.label = document.createElement(‘div’); var labelTextNode=document.createTextNode(label); this.label.appendChild(labelTextNode); this.element = document.createElement(‘div’); this.element.className = ‘input-field’; this.element.appendChild(this.label); this.element.appendChild(this.textArea); } extend(TextareaField,Field); TextareaField.prototype.getValue= function(){ return this.textarea.value; } var SelectField =function(id,label){ Field.call(this,id); this.select = document.createElement(‘select’); this.select.id = id; this.label = document.createElement(‘label’); var labelTextNode = document.createTextNode(label); this.label.appendChild(labelTextNode); this.element = document.createElement(‘div’); this.element.className = ‘input-field’; this.element.appendChild(this.label); this.element.appendChild(this.select); } extend(SelectField,Field); SelectField.prototype.getValue= function(){ returnthis.select.options[this.select.selectedIndex].value; } 这是组合模式大放光彩的地方 。无论有多少表单域,对整个组合对象执行操作只需要一个函数调用即可: var contactForm =new CompositeForm(‘contact-form’,’POST’,’contact.php’); contactForm.add(new InputField(‘firest-name’,”First Name”)); contactForm.add(new InputField(‘last-name’,”Last Name”)); contactForm.add(new InputField(‘address’,”Address”)); contactForm.add(new InputField(‘city’,”City”)); contactForm.add(new SelectField(‘state’,”State”,stateArray)); //var stateArray =[{‘a1’,’Alabama’},…] contactForm.add(new InputField(‘zip’,”Zip”)); contactForm.add(newTextareaField(‘comments,”Comments”)); addEvent(window,’unload’,contactForm.save); 可以把save的调用绑定到某个事件上,也可以用setInterval周期性地调用这个函数。为这个组合对象添加其他操作也很简单。如下一节所示,验证数据、恢复先前保存的数据以及将表单重设为默认状态这些操作都可以如法炮制。 现在基本框架已经设计好了,要为FormItem接口添加其他操作很容易。首先是修改这个接口: var FormItem = new Interface(‘FormItem’,[“save”,”restore”]); 然后是在叶对象上实现这些操作。在本例中只要为超类Field添加这些操作以供那些子类继承即可: Field.prototype.restore= function(){ this.element.value = getCookie(this.id); } 最后,为组合对象添加同样的操作: CompositeForm.prototype.restore= function(){ for(vari=0,len=this.formComponents.length;i this.formComponents[i].restore(); } } 在实现中加入下面这行代码后就可以窗口加载时恢复所有表单域的值: addEvent(window,’load’,contactForm.restore); 到目前为止只有一个组合对象类。如果设计目标要求对操作的调用 有更多粒度上的控制,那么,可能添加更多层次的组合对象类,而不必改变其类。假设我们想要对表单的某些部分执行save和restore操作,而不影响到其他部分,有一个解决办法是逐一在各个域上执行这些操作: firestName.restore(); lastName.restore(); … 但在不知道表单具体会有哪些域的情况下,这种方法并不管用。在层次体系中创建新的层次是一个更好的选择。我们可以把域组织在域集(fieldset)中,每一个域集都是一个实现了FormItem接口的组合对象。在域集上调用restore将导致在其所有子对象上调用restore。 创建CompositeFieldset类并不要求为此修改其他类。因为composite接口隐藏了所有内部实现细节,我们可以自由选用某种数据结构来存储子对象。作为示范,我们在些使用一个对象来存储CompositeFieldset的子对象,而不像CompositeForm那样使用数组: varCompositeFieldset = function(id,legendText){ //implements composite,FormItem this.components = {}; this.element = document.createElement(“fieldset”); this.element.id = id; if(legendText){ this.legend =document.createElement(‘legend’); this.legend.appendChild(document.create.TextNode(legendText); this.element.appendChild(this.legend); } } CompositeFieldset.prototype.add= function(child){ Interface.ensureImplements(child,Composite,FormItem); this.components[child.getElement().id] =child; this.element.appendChild(child.getElement()); } CompositeFieldset.prototype.remove= function(child){ delete this.components[child.getElement().id] } CompositeFieldset.prototype.getChild= function(child){ if(this.components[id] != undefined){ return this.components[id]; }else { return null; } } CompositeFieldset.prototype.save= function(child){ for(var id in this.components){ if(!this.components.hasOwnProperty(id))continue; this.components[id].save(); } } CompositeFieldset.prototype.restore= function(child){ for(var id in this.components){ if(!this.components.hasOwnProperty(id))continue; this.components[id].restore(); } } CompositeFieldset.prototype.getElement=function(child){ return this.element; } 汇合起来
向FormItem添加操作
向层次体系中添加类