写点什么

【设计模式】组合迭代器

用户头像
Brave
关注
发布于: 刚刚

一,前言


前面我们已经了解了两种设计模式:迭代器模式、组合模式


1,迭代器模式 :


封装集合的遍历方式,使不同集合可以使用相同的方式进行遍历
复制代码


2,组合模式 :


将对象组合成树形结构以表示"部分-整体"的层次结构,可以使用相同的方法处理"分支"和"叶子"节点对象
复制代码


那么,如果遍历一颗“树”,要如何实现呢?

可以使用以上两种模式构造“组合迭代器”模式;


二,组合迭代器思想

组合迭代器模式:用于遍历组合模式树形结构中的所有组件,在遍历过程中可以对返回的组件对象进行筛选和操作
组合迭代器核心思想:递归迭代,即让"分支"节点拥有将子对象集创建为迭代器对象的能力
在组合模式中我们将"分支"和"叶子"节点对象继承自相同的组件,从而视分支和叶子节点为相同对象进行处理
那显然组合迭代器需要"分支"节点对象继续递归迭代其子对象集,直至"叶子"节点位置,所以这里我们需要拓展组件对象,使"分支"节点可以创建迭代器用于迭代子对象集
当然由于分支和叶子节点都继承自该组件,所以在"分支"节点具备创建子对象迭代器功能的同时,"叶子"节点对象也会拥有迭代子对象的功能,但是"叶子"节点下已无子对象,所以在"叶子"节点中我们做一个空的实现即可(我们这里采用一个空的迭代器)
复制代码



三,组合迭代器


1,在组合模式的基础上,拓展组件对象 MenuComponent,使之可以将分支节点对象中包含的对象集创建为迭代器对象:

package com.brave.compositeIterator;
import java.util.Iterator;
/** * 菜单组件(叶子节点,分支节点)的抽象类 * 作用:为 叶子节点(菜单项) 和 分支节点(菜单) 提供统一接口 * 有些方式只对菜单有用,有些只对菜单项有用,这里写上父类默认实现,不重写默认无效抛出异常 * @author Brave * */public abstract class MenuComponent {
/** 增 删 查 **/ public void add(MenuComponent menuComponent){ throw new UnsupportedOperationException(); }
public void remove(MenuComponent menuComponent){ throw new UnsupportedOperationException(); }
public MenuComponent getChild(int i){ throw new UnsupportedOperationException(); }
/** 一些操作 **/ public String getName(){ throw new UnsupportedOperationException(); }
public String getDesc(){ throw new UnsupportedOperationException(); }
public double getPrice(){ throw new UnsupportedOperationException(); }
public boolean isVegetarian() { throw new UnsupportedOperationException(); }
/** 打印 **/ public void print(int level){ throw new UnsupportedOperationException(); }
/** * 创建迭代器方法,使分支节点对象可以将子对象集创建为迭代器对象,用于迭代 */ public abstract Iterator createIterator();
}
复制代码


2,在组合模式的基础上,分支节点继承自拓展后的组件对象 MenuComponent,菜单(分支)节点需添加对新方法 createIterator 的实现,这里我们需要返回一个组合迭代器(组合迭代器的核心):

package com.brave.compositeIterator;
import java.util.Iterator;import java.util.Stack;
/** * 组合迭代器: * 实现了迭代器Iterator * @author Brave * */public class CompositeIterator implements Iterator{
// 堆栈数据结构:后进先出 pop取后删掉,peek只取不删 Stack<Iterator> stack = new Stack<Iterator>();
/** * 将顶层迭代器对象抛入一个堆栈数据结构中 * @param iterator 顶层迭代器对象 */ public CompositeIterator(Iterator iterator) { stack.push(iterator); }
@Override public boolean hasNext() { if(stack.empty()){ return false; }else{ // 如果不为空,从堆顶取出迭代器 Iterator iterator = stack.peek(); //如果没有下一个元素,弹出堆栈 if(!iterator.hasNext()){ stack.pop();// 移除此迭代器对象 return hasNext();//递归检查下一个迭代器 }else{ return true; } } }
@Override public Object next() {
if(hasNext()){ Iterator iterator = stack.peek(); MenuComponent component = (MenuComponent)iterator.next();
//如果是菜单对象,将本层迭代器放入堆栈,准备读取 if(component instanceof Menu){ stack.push(component.createIterator()); } return component; }else{ return null; }
}
}
复制代码


package com.brave.compositeIterator;
import java.util.ArrayList;import java.util.Iterator;
/** * 菜单对象 * 菜单(Menu)和菜单项(MenuItem)都拓展自MenuComponent:可以使用相同方式来菜单和菜单项对象 * 对于菜单对象:能够添加,删除,查询菜单项并遍历下级菜单项 * @author Brave * */public class Menu extends MenuComponent{
ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
String name; //名称 String desc; //描述
public Menu(String name, String desc) { this.name = name; this.desc = desc; }
/** 重写增 删 查 **/ @Override public void add(MenuComponent menuComponent) { menuComponents.add(menuComponent); }
@Override public void remove(MenuComponent menuComponent){ menuComponents.remove(menuComponent); }
@Override public MenuComponent getChild(int i){ return menuComponents.get(i); }
/** 重写getter **/ @Override public String getName() { return name; }
@Override public String getDesc() { return desc; }
/** 打印 **/ @Override public void print(int level){
String pre = ""; for(int i=0; i<level; i++){ pre = pre+"--"; }
System.out.println(pre + "*******************************************"); System.out.println(pre + "name = " + getName() + ", Desc = " + getDesc()); System.out.println(pre + "*******************************************");
Iterator<MenuComponent> iterator = menuComponents.iterator();
while(iterator.hasNext()){ MenuComponent menuComponent = iterator.next(); menuComponent.print(level + 2); } }
/** * 将分支节点的子对象集创建为迭代器对象(用于迭代组件) */ @Override public Iterator createIterator() { return new CompositeIterator(menuComponents.iterator()); }
}
复制代码


3,菜单项(叶子)节点也需添加对新方法 createIterator 的实现,由于菜单项已经是“叶子”节点,不会在有下级节点,也无需迭代,这里我们可以在 createIterator 的实现中使用一个空迭代器对象:

package com.brave.compositeIterator;
import java.util.Iterator;
/** * 空迭代器对象 * 因为菜单项已经是叶子节点,下边没有可迭代的对象 * @author Brave * */public class NullIterator implements Iterator {
@Override public boolean hasNext() { return false; }
@Override public Object next() { return null; }
}
复制代码


package com.brave.compositeIterator;
import java.util.Iterator;
/** * 菜单项对象 * 菜单(Menu)和菜单项(MenuItem)都拓展自MenuComponent:可以使用相同的方式吃力菜单和菜单项对象 * 对于菜单项对象:不能够添加,删除,查询菜单项和菜单项,只能获取菜单项对象的属性 * @author Brave * */public class MenuItem extends MenuComponent{
String name; //名称 String desc; //描述 boolean vegetarian; //是否素食 double price; //价格
public MenuItem(String name, String desc, boolean vegetarian, double price) { this.name = name; this.desc = desc; this.vegetarian = vegetarian; this.price = price;
}
/** 重写getter **/ @Override public String getName() { return name; }
@Override public String getDesc() { return desc; }
@Override public double getPrice() { return price; }
@Override public boolean isVegetarian() { return vegetarian; }
/** 打印 **/ @Override public void print(int level){
String pre = ""; for(int i=0; i<level; i++){ pre = pre+"--"; }
System.out.println(pre + "name = " + getName() + ", Desc = "+ getDesc() + ", Price = " + getPrice());
}
/** 返回空迭代器 :因为菜单已经是叶子节点,下边没有可迭代的对象,故返回一个空迭代器对象 **/ @Override public Iterator createIterator() { return new NullIterator(); }
}
复制代码


在这个例子中,我们对菜单项对象添加了一个新的属性 vegetarian:表示菜品是否为素食;用于后续展示组合迭代器的作用;


4,和组合模式类似,创建一个树形结构的菜单,但这里我们将菜单的输出封装到一个招待员类,可以输出全部菜单和仅素食菜单:

package com.brave.compositeIterator;
import java.util.Iterator;
/** * 招待员类 * 面向接口编程 MenuComponent, Iterator * 封装了Menu的各种遍历 * @author Brave * */public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; }
public void printMenu(){ allMenus.print(1); }
public void printVegetarianMenu() {
// 将菜单创建为一个组合迭代器 Iterator iterator = allMenus.createIterator();
while(iterator.hasNext()){
try{ MenuComponent menuComponent = (MenuComponent)iterator.next(); if(menuComponent.isVegetarian()){ menuComponent.print(1); } }catch(UnsupportedOperationException e){ // 暂不处理 : 菜单对象会进入Catch(因为没有重写isVegetarian) } } }
}
复制代码


通过招待员类对菜单的进一步封装,我们在客户端调用时,可以选中输出全部菜单或者仅素食菜单:

package com.brave.compositeIterator;
/** * 组合模式的测试类 * 由于菜单(Menu)和菜单项(MenuItem)都集成自菜单组件(MenuComponent):菜单和菜单项间可以自由的进行组合,并可以使用相同的处理方式 * @author Brave * */public class MentTest {
public static void main(String[] args) {
// 创建早餐菜单 MenuComponent breakFastMenu = new Menu("菜单1","早餐菜单"); breakFastMenu.add(new MenuItem("煎饼果子", "鸡蛋,果子", false, 5.00)); breakFastMenu.add(new MenuItem("豆浆", "豆浆", true, 2.00)); breakFastMenu.add(new MenuItem("豆腐脑", "豆腐脑", true, 3.00)); breakFastMenu.add(new MenuItem("锅巴菜", "锅巴,卤子", true, 4.00)); breakFastMenu.add(new MenuItem("茶叶蛋", "鸡蛋", false, 1.00)); breakFastMenu.add(new MenuItem("云吞", "猪肉,面皮,高汤", false, 4.00));
// 创建晚餐菜单 MenuComponent dinerMenu = new Menu("菜单2","晚餐菜单"); dinerMenu.add(new MenuItem("京酱肉丝", "肉丝,葱,豆皮", false, 25.00)); dinerMenu.add(new MenuItem("五谷丰登", "各种菜", true, 10.00)); dinerMenu.add(new MenuItem("水果沙拉", "各种水果和沙拉", true, 15.00)); dinerMenu.add(new MenuItem("宫保鸡丁", "鸡丁,葱,姜,蒜,微辣", false, 15.00));
// 创建甜点菜单 MenuComponent cakeMenu = new Menu("菜单3","甜点菜单"); cakeMenu.add(new MenuItem("慕斯蛋糕", "黄油,淡奶油,蛋糕", true, 25.00)); cakeMenu.add(new MenuItem("黑森林蛋糕", "巧克力,奶油,樱桃酒,蛋糕", true, 25.00)); cakeMenu.add(new MenuItem("提拉米苏", "芝士,蛋黄,咖啡,可可粉", true, 25.00));
// 组装菜单:甜点菜单添加到晚餐,早餐和晚餐添加到总菜单 dinerMenu.add(cakeMenu); MenuComponent Menu = new Menu("总菜单","早餐+晚餐"); Menu.add(breakFastMenu); Menu.add(dinerMenu);
// 展示菜单 Waitress waitress = new Waitress(Menu); // 全部 waitress.printMenu(); // 仅素食 waitress.printVegetarianMenu();
}}
复制代码


输出如下:

--*******************************************--name = 总菜单, Desc = 早餐+晚餐--*******************************************------*******************************************------name = 菜单1, Desc = 早餐菜单------*******************************************----------name = 煎饼果子, Desc = 鸡蛋,果子, Price = 5.0----------name = 豆浆, Desc = 豆浆, Price = 2.0----------name = 豆腐脑, Desc = 豆腐脑, Price = 3.0----------name = 锅巴菜, Desc = 锅巴,卤子, Price = 4.0----------name = 茶叶蛋, Desc = 鸡蛋, Price = 1.0----------name = 云吞, Desc = 猪肉,面皮,高汤, Price = 4.0------*******************************************------name = 菜单2, Desc = 晚餐菜单------*******************************************----------name = 京酱肉丝, Desc = 肉丝,葱,豆皮, Price = 25.0----------name = 五谷丰登, Desc = 各种菜, Price = 10.0----------name = 水果沙拉, Desc = 各种水果和沙拉, Price = 15.0----------name = 宫保鸡丁, Desc = 鸡丁,葱,姜,蒜,微辣, Price = 15.0----------*******************************************----------name = 菜单3, Desc = 甜点菜单----------*******************************************--------------name = 慕斯蛋糕, Desc = 黄油,淡奶油,蛋糕, Price = 25.0--------------name = 黑森林蛋糕, Desc = 巧克力,奶油,樱桃酒,蛋糕, Price = 25.0--------------name = 提拉米苏, Desc = 芝士,蛋黄,咖啡,可可粉, Price = 25.0
--name = 豆浆, Desc = 豆浆, Price = 2.0--name = 豆腐脑, Desc = 豆腐脑, Price = 3.0--name = 锅巴菜, Desc = 锅巴,卤子, Price = 4.0--name = 五谷丰登, Desc = 各种菜, Price = 10.0--name = 水果沙拉, Desc = 各种水果和沙拉, Price = 15.0--name = 慕斯蛋糕, Desc = 黄油,淡奶油,蛋糕, Price = 25.0--name = 黑森林蛋糕, Desc = 巧克力,奶油,樱桃酒,蛋糕, Price = 25.0--name = 提拉米苏, Desc = 芝士,蛋黄,咖啡,可可粉, Price = 25.0--name = 慕斯蛋糕, Desc = 黄油,淡奶油,蛋糕, Price = 25.0--name = 黑森林蛋糕, Desc = 巧克力,奶油,樱桃酒,蛋糕, Price = 25.0--name = 提拉米苏, Desc = 芝士,蛋黄,咖啡,可可粉, Price = 25.0
复制代码



四,结论


从以上示例可以看出,使用组合迭代器模式,可以对树形结构的组合迭代器进行遍历迭代,在迭代过程中返回每一个组件对象进行处理;


其实,对于树形结构的迭代,还可以使用其他的方式;

这里,我们将组合模式和迭代器模式合二为一,也展示了设计模式的强大;

对于类似 java 这种面向对象语言,其特性在于继承,多态和封装;

而设计模式正是对其特性的淋漓尽致的体现;

设计模式仅仅是一招半式,融会贯通才能发挥真正威力;

编程,不仅是技术,更是艺术;


用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【设计模式】组合迭代器