1.定义
允许你将对象组合成树形结构来表现"整体结构"层次结构.组合能让客户以一致的方式处理个别对象以及对象组合.
把整体和局部都当作一个对象,这就需要继承相同的类或者实现相同的接口,我们沿着这个思路实现代码.
2.代码实现
以 https://www.cnblogs.com/lishuaiqi/p/11291839.html 这个例子继续举例.
如果我们除了三个菜单, 早餐,午餐,晚餐这三种菜单,还需要拓展子菜单,也就是需要在晚餐后加上饭后甜点菜单,或者在甜点菜单加上饮品菜单等等之类的,这就形成了一个树形结构,如果继续用迭代器模式的话就有点不适合了,因为类型太多,还有子类型,需要增加很多不必要的判断.
我们用组合加上迭代器来实现这个功能
首先定义组合类,因为我们需要把 菜单 (组合) 和 菜单项(叶节点:没有孩子节点的节点) 都当成组合
package composite; import java.util.Iterator; 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 getDescription() { throw new UnsupportedOperationException(); } public double getPrice() { throw new UnsupportedOperationException(); } public boolean isVegetarian() { throw new UnsupportedOperationException(); } public void print() { throw new UnsupportedOperationException(); } public abstract Iterator createIterator(); }
都是基本操作,增加,删除,获取子节点等.还有获取一些元素,打印等功能,最后一个方法是抽象方法 createIterator 因为我们需要把遍历操作使用迭代器模式封装到迭代器中实现.
我们实现菜单,继承组合抽象类
package composite; import java.util.ArrayList; import java.util.Iterator; public class Menu extends MenuComponent{ ArrayList menuComponents = new ArrayList(); String name; String description; public Menu(String name, String description) { this.name = name; this.description = description; } @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 (MenuComponent) menuComponents.get(i); } @Override public String getName() { return name; } @Override public String getDescription() { return description; } @Override public void print() { System.out.print(" "+getName()); System.out.println(" , "+getDescription()); System.out.println(" ---------------- "); Iterator iterator = menuComponents.iterator(); while (iterator.hasNext()) { MenuComponent menuComponent = (MenuComponent)iterator.next(); menuComponent.print(); } } @Override public Iterator createIterator() { return new CompositeIterator(menuComponents.iterator()); } }
因为菜单只有名字和描述属性,我们只定义这两种变量,并实现相应的方法,迭代器后面会补上.在print方法中,通过遍历list的itreator来获取root根节点下的所有空间信息,因为组合和叶节点都实现了print 方法,我们可以直接调用print方法即可.
接下来定义菜单包含的内容---菜单项,因为没有子节点,所以也叫叶节点
package composite; import java.util.Iterator; public class MenuItem extends MenuComponent{ String name; String description; boolean Vegetarian; double price; public MenuItem(String name, String description, boolean vegetarian, double price) { super(); this.name = name; this.description = description; Vegetarian = vegetarian; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isVegetarian() { return Vegetarian; } public void setVegetarian(boolean vegetarian) { Vegetarian = vegetarian; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } //组合模式 @Override public void print() { System.out.print(" " + getName()); if (isVegetarian()) { System.out.print("(V)"); } System.out.println( " , " + getPrice()); System.out.println(" --" + getDescription()); } @Override public Iterator createIterator() { return new NullIterator(); } }
这边四大属性都有,名字,描述,是否是素食,价格都有,所以我们的服务员最后要打印的也就是这个类的信息.
因为这个模块是使用了迭代器来遍历元素,所以我们定义组合的迭代器
package composite; import java.util.Iterator; import java.util.Stack; public class CompositeIterator implements Iterator { Stack stack = new Stack(); public CompositeIterator(Iterator iterator) { stack.push(iterator); } @Override //这个方法会判断下一个子节点是否有子节点 public boolean hasNext() { if (stack.isEmpty()) { //空栈则返回false return false; } else { //获取第一个元素,peek不会删除元素 Iterator iterator = (Iterator) stack.peek(); if (!iterator.hasNext()) {//如果元素没有下一个 //出栈并且递归调用,pop会删除第一个元素 //通过pop我们把没有子节点的元素都给删除了 stack.pop(); return hasNext(); } else { //有下个元素则返回true return true; } } } @Override public Object next() { if (hasNext()) { //如果有子节点则取出栈的第一个元素 Iterator iterator = (Iterator) stack.peek(); //因为上面的pop操作,所以我们第一个元素肯定是有子节点的 MenuComponent component = (MenuComponent) iterator.next(); //只需要判断是那种类型即可,如果是菜单,则需要添加进栈中,因为我们只需要获取菜单项 if (component instanceof Menu) { stack.push(component.createIterator()); } return component; } return null; } }
这个功能有点复杂,解释都写在注释中了,这个就是遍历菜单项的,不包括菜单.
这边还有一个空的迭代器
package composite; import java.util.Iterator; public class NullIterator implements Iterator{ @Override public boolean hasNext() { return false; } @Override public Object next() { return null; } }
其实就是一直让hasNext为false就可以了,主要给MenuItem菜单项实现,因为菜单项就是子元素,没有子节点,所以给它返回 CompositeIterator 也没有意义.
还是服务员
package composite; import java.util.Iterator; public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; } public void printMenu() { allMenus.print(); } public void printVegetarianMenu() { Iterator iterator = allMenus.createIterator(); System.out.println("-------------VEGETRAIAN MENU--------------"); while (iterator.hasNext()) { MenuComponent menuComponent = (MenuComponent)iterator.next(); try { if (menuComponent.isVegetarian()) { menuComponent.print(); } } catch (Exception e) { } } } }
printVegetarianMenu 这个主要是利用迭代器来判断是否是蔬菜,这边用了 try catch 语句,如果出错也就是如果是菜单的话就会捕获异常,并跳过try的语句,相当于 continue语句.
测试类
package composite; public class MenuTestDriver { public static void main(String[] args) { //早餐菜单 MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast"); //午餐菜单 MenuComponent dinnerMenu = new Menu("DINNER MENU", "Lunch"); //晚餐菜单 MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner"); //晚餐甜点菜单 MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!"); //主菜单 MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined"); //菜单加入元素 allMenus.add(pancakeHouseMenu); allMenus.add(dinnerMenu); allMenus.add(cafeMenu); dinnerMenu.add(new MenuItem("Pasta", "a slice of sourdough bread", true, 3.89)); dinnerMenu.add(dessertMenu); dessertMenu.add(new MenuItem("Apple Pie", "Apple Pie", false, 3.21)); Waitress waitress = new Waitress(allMenus); waitress.printMenu(); System.out.println("------------------素食----------------"); waitress.printVegetarianMenu(); } }
结果
ALL MENUS , All menus combined ---------------- PANCAKE HOUSE MENU , Breakfast ---------------- DINNER MENU , Lunch ---------------- Pasta(V) , 3.89 --a slice of sourdough bread DESSERT MENU , Dessert of course! ---------------- Apple Pie , 3.21 --Apple Pie CAFE MENU , Dinner ---------------- ------------------素食---------------- -------------VEGETRAIAN MENU-------------- Pasta(V) , 3.89 --a slice of sourdough bread
3.总结
组合就是把树形结构的每个元素都当成一个类型,不管是 组合 还是叶节点都是相同对待,这就是透明性,对于客户来说菜单和菜单项都是一样的,通过调用相同的方法就能实现一样的功能.
思想很好理解,树形结构,大家都是一种类型.