深入理解JavaScript系列(40):设计模式之组合模式详解
介绍
组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
常见的场景有asp.net里的控件机制(即control里可以包含子control,可以递归操作、添加、删除子control),类似的还有DOM的机制,一个DOM节点可以包含子节点,不管是父节点还是子节点都有添加、删除、遍历子节点的通用功能。所以说组合模式的关键是要有一个抽象类,它既可以表示子元素,又可以表示父元素。
正文
举个例子,有家餐厅提供了各种各样的菜品,每个餐桌都有一本菜单,菜单上列出了该餐厅所偶的菜品,有早餐糕点、午餐、晚餐等等,每个餐都有各种各样的菜单项,假设不管是菜单项还是整个菜单都应该是可以打印的,而且可以添加子项,比如午餐可以添加新菜品,而菜单项咖啡也可以添加糖啊什么的。
这种情况,我们就可以利用组合的方式将这些内容表示为层次结构了。我们来逐一分解一下我们的实现步骤。
第一步,先实现我们的“抽象类”函数MenuComponent:
varMenuComponent=function(){ }; MenuComponent.prototype.getName=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.getDescription=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.getPrice=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.isVegetarian=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.print=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.add=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.remove=function(){ thrownewError("该方法必须重写!"); }; MenuComponent.prototype.getChild=function(){ thrownewError("该方法必须重写!"); };
该函数提供了2种类型的方法,一种是获取信息的,比如价格,名称等,另外一种是通用操作方法,比如打印、添加、删除、获取子菜单。
第二步,创建基本的菜品项:
varMenuItem=function(sName,sDescription,bVegetarian,nPrice){ MenuComponent.apply(this); this.sName=sName; this.sDescription=sDescription; this.bVegetarian=bVegetarian; this.nPrice=nPrice; }; MenuItem.prototype=newMenuComponent(); MenuItem.prototype.getName=function(){ returnthis.sName; }; MenuItem.prototype.getDescription=function(){ returnthis.sDescription; }; MenuItem.prototype.getPrice=function(){ returnthis.nPrice; }; MenuItem.prototype.isVegetarian=function(){ returnthis.bVegetarian; }; MenuItem.prototype.print=function(){ console.log(this.getName()+":"+this.getDescription()+","+this.getPrice()+"euros"); };
由代码可以看出,我们只重新了原型的4个获取信息的方法和print方法,没有重载其它3个操作方法,因为基本菜品不包含添加、删除、获取子菜品的方式。
第三步,创建菜品:
varMenu=function(sName,sDescription){ MenuComponent.apply(this); this.aMenuComponents=[]; this.sName=sName; this.sDescription=sDescription; this.createIterator=function(){ thrownewError("Thismethodmustbeoverwritten!"); }; }; Menu.prototype=newMenuComponent(); Menu.prototype.add=function(oMenuComponent){ //添加子菜品 this.aMenuComponents.push(oMenuComponent); }; Menu.prototype.remove=function(oMenuComponent){ //删除子菜品 varaMenuItems=[]; varnMenuItem=0; varnLenMenuItems=this.aMenuComponents.length; varoItem=null;
for(;nMenuItem<nLenMenuItems;){ oItem=this.aMenuComponents[nMenuItem]; if(oItem!==oMenuComponent){ aMenuItems.push(oItem); } nMenuItem=nMenuItem+1; } this.aMenuComponents=aMenuItems; }; Menu.prototype.getChild=function(nIndex){ //获取指定的子菜品 returnthis.aMenuComponents[nIndex]; }; Menu.prototype.getName=function(){ returnthis.sName; }; Menu.prototype.getDescription=function(){ returnthis.sDescription; }; Menu.prototype.print=function(){ //打印当前菜品以及所有的子菜品 console.log(this.getName()+":"+this.getDescription()); console.log("--------------------------------------------");
varnMenuComponent=0; varnLenMenuComponents=this.aMenuComponents.length; varoMenuComponent=null;
for(;nMenuComponent<nLenMenuComponents;){ oMenuComponent=this.aMenuComponents[nMenuComponent]; oMenuComponent.print(); nMenuComponent=nMenuComponent+1; } };