组合模式

本文为阅读《Javascript设计模式》一书后,总结部分内容而得。其内部的代码和截图都来源自该书。

使用组合模式的一个场景示例

想象一下,现在你需要维护一个个人信息的页面,当用户不同时,页面也可能会发生变化。比如,当用户为小明时,展示给他的页面是这样的

而当用户为小红时,展示给她的页面是这样的

我现在想要实现保存页面信息的功能,面对各种各样的可能页面,页面上的各种元素保存信息的方式也不同,比如select和input框,其取值方式就完全不同。我要如何通过统一的函数来保存这些页面信息呢?有一种方法,是通过每次保存页面时,都遍历页面所有元素,判断所有元素类型,并依据个元素类型执行对应的save函数。但这样很显然,会使代码看上去混乱且臃肿。

用组合模式实现保存功能则你可以用一条简单的命令,在多个子对象上激发递归行为,将保存功能委托给各个子对象来实现,让每个子对象都知道如何保存自己本身的信息,父对象只是起到一个传递调用的功能。

组合模式

组合对象的结构

一个组合对象由一些别的组合对象和叶对象组成。不再包含子对象的叫叶对象,其下一层级仍然包含子对象的叫组合对象。结构图如下图:

什么情况下使用组合模式

  • 存在一批组织成某种层次体系的对象
  • 希望对这批对象或其中一部分对象实施一个操作

一个使用组合模式的示例----表单信息存储

现在我们来看,使用组合模式,如何实现在本文最开始提出的那个表单信息存储功能。
下图为表单的结构

明确组合对象及叶对象要具备的函数

首先,我们要明确,想让组合对象以及叶对象实现哪些接口,即具备哪些方法。在本例中,我想让他们实现两个接口Composite和FormItem
var Composite=new Interface('Composite',['add''remove','getChild']);
var FormItem=new Interface('FormItem',['save']);

定义叶对象

所有叶对象,都继承了Field,具有Field对象所定义的所有方法,即add,remove,getChild以及save
我们先实现该父类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.'); 
};
父类定义好后,我们来定义各个叶对象。在这个应用中,我们假定页面是由input框、select框以及textarea组成,那么我们来定义这三个叶对象。
其实三个对象只有getValue方法是不同的。
/* InputField class. */

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;
};

定义根级组合对象

(注意,在当前这个实例中,根据要实现的功能,我只将页面划分为了根级组合对象和叶对象,并没有中间层级的组合对象)
在这段定义中,我们对所有添加进组合对象的叶对象进行了判断,只有该对象实现了Composite, FormItem两个接口,才添加进组合对象成为叶对象。
</pre><pre name="code" class="javascript">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; 
};

组合对象和叶对象的汇合

两类对象都定义完了,接下来,就是将他们联系到一起,并且实现我们想要实现的功能了。
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);

总结下通过该模式实现保存功能的流程,首先,我们对页面添加了监听,一旦页面加载,就调用组合对象的save方法,而该save函数,遍历了其下的所有叶对象,并调用该对象的save函数来实现其save函数。

向接口添加方法

如果现在有业务更改,除了save函数,我还要在每次页面加载后对所有对象执行restore操作。为了增加这一功能,我想要修改FormItem接口,在接口中增加一个函数。如何实现呢?
//修改接口的定义
var FormItem = new Interface('FormItem', ['save', 'restore']);
//为各个叶对象、组合对象添加对应的函数
Field.prototype.restore = function() {
  this.element.value = getCookie(this.id);
};

CompositeForm.prototype.restore = function() {
  for(var i = 0, len = this.formComponents.length; i < len; i++) {
    this.formComponents[i].restore();
  }
};
//添加触发函数实现功能
addEvent(window, 'load', contactForm.restore);

更复杂一点的示例

好,第一个示例我们已经实现了,更进一步,如果现在我想对某几个叶对象的组合执行某些操作呢?比如说, 我想在一定条件被触发时,对某几个叶对象执行save函数,而不是对所有叶对象执行该函数。这时,我们就需要添加非根级的组合对象了,也就是说,把这几个想要操作的叶对象,视为一个组合对象。

定义非根级组合对象

对于当前的非根级组合对象,并不受到根级组合对象的影响,比如,根级组合对象是通过数组来存储其下的组合对象和叶对象,各非根级组合对象,可以用数组存储叶对象,也可以用对象来存储叶对象。那么在下面,我们就举一个用对象来存储叶对象的代码示例
var CompositeFieldset = function(id, legendText) { // implements Composite, FormItem
  this.components = {};

  this.element = document.createElement('fieldset');
  this.element.id = id;

  if(legendText) { // Create a legend if the optional second 
                   // argument is set.
    this.legend = document.createElement('legend');
    this.legend.appendChild(document.createTextNode(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(id) {
  if(this.components[id] != undefined) {
    return this.components[id];
  }
  else {
    return null;
  }
};

CompositeFieldset.prototype.save = function() {
  for(var id in this.components) {
    if(!this.components.hasOwnProperty(id)) continue;
    this.components[id].save();
  }
};

CompositeFieldset.prototype.restore = function() {
  for(var id in this.components) {
    if(!this.components.hasOwnProperty(id)) continue;
    this.components[id].restore();
  }
};

CompositeFieldset.prototype.getElement = function() { 
  return this.element; 
};

组合对象和叶对象汇合

var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');
//一个组合对象
var nameFieldset = new CompositeFieldset('name-fieldset');
nameFieldset.add(new InputField('first-name', 'First Name'));
nameFieldset.add(new InputField('last-name', 'Last Name'));
contactForm.add(nameFieldset);
//又一个组合对象
var addressFieldset = new CompositeFieldset('address-fieldset');
addressFieldset.add(new InputField('address', 'Address'));
addressFieldset.add(new InputField('city', 'City'));
addressFieldset.add(new SelectField('state', 'State', stateArray));
addressFieldset.add(new InputField('zip', 'Zip'));
contactForm.add(addressFieldset);

contactForm.add(new TextareaField('comments', 'Comments'));

body.appendChild(contactForm.getElement());

addEvent(window, 'unload', contactForm.save);
addEvent(window, 'load', contactForm.restore);
//对指定组合对象的操作
addEvent('save-button', 'click', nameFieldset.save);
addEvent('restore-button', 'click', nameFieldset.restore);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值