《javascript设计模式》笔记之第九章:组合模式
之前一直都是按照书的结构顺序做总结,觉得好像不是很好,现在试着完全按照自己的理解做总结。例子还是书上的例子。
一:组合模式的作用:
在web开发中,主要用于创建嵌套的html结点,使得我们方便的把各种结点连接起来,并且提供简易的操作。
结构就像我们的文件结构一样讲Composite理解为文件夹,Leaf理解为文件就好理解了。
三:例子一,创建一个组合的表单
需求:试想着我们想要构建一个表单,但是表单域经常要被产品经理修改,我们怎样才能利用js快速的搭建这个form呢?此外,我们有个功能,就是可以保存用户填写的表单,那么我们一般的做法是手动把一个个表单域保存到cookie中,如何才能通过一个操作就完成所有表单域的保存呢?
利用组合模式实现是这样的:
思路:创建一个组合表单类CompositeForm,这个类有操作表单域的方法add(),和remove(),传入的参数就是对应的表单域类(InputField,TextareaField等),这样我们就可以很方便的组合表单了。对于保存表单域的功能,我们只要给CompositeForm弄一个save()方法就可以了,这个save()方法就遍历它下面的表单域,自动的保存每个表单域。
具体实现:
步骤一:
创建接口,保证表单和表单域都有必须的方法
var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = new Interface('FormItem', ['save']);
步骤二:
定义组合表单类CompositeForm
var CompositeForm = function(id, method, action) { // implements Composite, FormItem 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(var i = 0, len = this.formComponents.length; i < len; i++) { if(this.formComponents[i] === child) { this.formComponents.splice(i, 1); // Remove one element from the array at // position i. break; } } }; CompositeForm.prototype.getChild = function(i) { return this.formComponents[i]; }; CompositeForm.prototype.save = function() { for(var i = 0, len = this.formComponents.length; i < len; i++) { this.formComponents[i].save(); } }; CompositeForm.prototype.getElement = function() { return this.element; };
步骤三:
创建Field类,用来派生出表单域的类
var Field = function(id) { // implements Composite, FormItem 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 on the class Field.'); };
步骤三
创建特定的表单域类
var InputField = function(id, label) { // implements Composite, FormItem Field.call(this, id); this.input = document.createElement('input'); this.input.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.input); }; extend(InputField, Field); // Inherit from Field. InputField.prototype.getValue = function() { return this.input.value; }; /* TextareaField class. */ 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('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.textarea); }; extend(TextareaField, Field); // Inherit from Field. TextareaField.prototype.getValue = function() { return this.textarea.value; }; /* SelectField class. */ var SelectField = function(id, label) { // implements Composite, FormItem 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); // Inherit from Field. SelectField.prototype.getValue = function() { return this.select.options[this.select.selectedIndex].value; };
步骤四:
使用
var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php'); contactForm.add(new InputField('first-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 = [{'al', 'Alabama'}, ...] contactForm.add(new InputField('zip', 'Zip')); contactForm.add(new TextareaField('comments', 'Comments')); addEvent(window, 'unload', contactForm.save);
点评:注意CompositeForm里面有formComponents这个属性,里面包含的是每一个表单域的对象,这样的话当我们要用到他们的时候就不用操作DOM来获取了。save()这个方法可以看一下,具体做法就是遍历表单的组件,逐一调用它们的save()方法。由于表单的结构要求,表单域不能嵌套form元素,所以这个例子还不能很好的诠释组合模式,最好情况下组合模式的组合程度是很高的。步骤四中的使用方法中,我们可以发现表单的动态创建是很简单的,保存方法的使用也是很简单,只要操作最contactForm这个变量就可以了。
书上还有一个对这个表单的扩展,这里就不展开了。
四:例子二,Image gallery
下面来看一个更好的例子:
需求:我们要创建一个图片集,图片集里面有图片集和图片,图片集下面又有图片集和图片。
思路:创建一个DynamicGallery类,这个类有add()、remove()等方法。add()方法可以传入DynamicGallery类和GalleryImage类。
实现:
步骤一:创建接口,保证上面提到的两个类符合要求
var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); var GalleryItem = new Interface('GalleryItem', ['hide', 'show']);
步骤二:创建DynamicGallery类
var DynamicGallery = function(id) { // implements Composite, GalleryItem this.children = []; this.element = document.createElement('div'); this.element.id = id; this.element.className = 'dynamic-gallery'; } DynamicGallery.prototype = { // Implement the Composite interface. add: function(child) { Interface.ensureImplements(child, Composite, GalleryItem); this.children.push(child); this.element.appendChild(child.getElement()); }, remove: function(child) { for(var node, i = 0; node = this.getChild(i); i++) { if(node == child) { this.formComponents[i].splice(i, 1); break; } } this.element.removeChild(child.getElement()); }, getChild: function(i) { return this.children[i]; }, // Implement the GalleryItem interface. hide: function() { for(var node, i = 0; node = this.getChild(i); i++) { node.hide(); } this.element.style.display = 'none'; }, show: function() { this.element.style.display = 'block'; for(var node, i = 0; node = this.getChild(i); i++) { node.show(); } }, // Helper methods. getElement: function() { return this.element; } };
第三步:创建GalleryImage类
var GalleryImage = function(src) { // implements Composite, GalleryItem this.element = document.createElement('img'); this.element.className = 'gallery-image'; this.element.src = src; } GalleryImage.prototype = { // Implement the Composite interface. add: function() {}, // This is a leaf node, so we don't remove: function() {}, // implement these methods, we just getChild: function() {}, // define them. // Implement the GalleryItem interface. hide: function() { this.element.style.display = 'none'; }, show: function() { this.element.style.display = ''; // Restore the display attribute to its // previous setting. }, // Helper methods. getElement: function() { return this.element; } };
第四步:使用
var topGallery = new DynamicGallery('top-gallery'); topGallery.add(new GalleryImage('/img/image-1.jpg')); topGallery.add(new GalleryImage('/img/image-2.jpg')); topGallery.add(new GalleryImage('/img/image-3.jpg')); var vacationPhotos = new DynamicGallery('vacation-photos'); for(var i = 0; i < 30; i++) { vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg')); } topGallery.add(vacationPhotos); topGallery.show(); // Show the main gallery, vacationPhotos.hide(); // but hide the vacation gallery.
总结:在这个例子中,我们就可以把DynamicGallery当作是上面那个结构图的Composite,GalleryImage当作是Leaf来理解了。DynamicGallery的add方法可以添加DynamicGallery,这样我们就可以创造出不同的结构了