本章节将学组合模式,来解决上一节遗留的问题。这个模式能够创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。场景在上一章已经描述清楚了,下面就直接来实现。
代码示例
创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项。该接口提供一些默认方法,默认行为都是抛出不支持异常,菜单和菜单项针对各自的情况进行重写。
123456789101112131415161718192021222324252627282930313233public 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();}}实现菜单项
12345678910111213141516171819202122232425262728293031323334353637383940414243public class MenuItem extends MenuComponent {String name;String description;boolean vegetarian;double price;public MenuItem(String name, String description, boolean vegetarian, double price) {this.name = name;this.description = description;this.vegetarian = vegetarian;this.price = price;}public String getName() {return name;}public String getDescription() {return description;}public double getPrice() {return price;}public boolean isVegetarian() {return vegetarian;}public void print() {System.out.print(" " + getName());if (vegetarian) {System.out.print("(v)");}System.out.println(", " + getPrice());System.out.println(" -- " + getDescription());}}实现组合菜单,这里的
menueComponents
是关键,其中可以存放MenuItem
或者Menu
本身,这里的print()
会递归打印所有MenuComponent
.1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class Menu extends MenuComponent {ArrayList<MenuComponent> menueComponents = new ArrayList();String name;String description;public Menu(String name, String description) {this.name = name;this.description = description;}public void add(MenuComponent menuComponent) {menueComponents.add(menuComponent);}public void remove(MenuComponent menuComponent) {menueComponents.remove(menuComponent);}public MenuComponent getChild(int i) {return menueComponents.get(i);}public String getName() {return name;}public String getDescription() {return description;}public void print() {System.out.print("\n" + getName());System.out.println(", " + getDescription());System.out.println("-----------------------");Iterator<MenuComponent> iterator = menueComponents.iterator();while (iterator.hasNext()) {MenuComponent menuComponent = iterator.next();menuComponent.print();}}}
这个模式在统一的接口中定义了两种实现需要的方法,然而两类方法又完全不相干,通过默认抛出不支持异常的方式来规避误操作,感觉有点不够干净。书上是这么说的,组合模式以单一责任设计原则换取透明性。什么是透明性?通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说一个元素究竟是组合还是叶节点对客户是透明的。如果说设计成多个不同接口的实现方式,客户的代码将必须用条件语句和instanceof
操作符处理不同类型的节点。所以这是一个折衷的案例,还是有它存在的意义的。
组合迭代器
其实在print()
方法中已经体现出迭代器了,但是如果我们需要使用迭代器来遍历整个组合的话,还是需要提供额外的功能。下面就来看下,怎么把迭代器组合加进来。
MenuComponent
接口添加createIterator()
抽象方法。1234public abstract class MenuComponent {......public abstract Iterator<MenuComponent> createIterator();}
创建一个空的迭代器,供
MenuItem
使用,因为该类中没有需要迭代的集合。Java8开始Iterator.remove()
默认抛出不支持异常,所以这里不需要重写。1234567891011public class NullIterator implements Iterator<MenuComponent> {public boolean hasNext() {return false;}public MenuComponent next() {return null;}}创建一个迭代器,供
Menu
使用。这个迭代器用到了栈和递归123456789101112131415161718192021222324252627282930313233343536373839404142434445public class CompositeIterator implements Iterator<MenuComponent> {/*** 保存Iterator的栈*/Stack<Iterator<MenuComponent>> stack = new Stack<>();public CompositeIterator(Iterator<MenuComponent> iterator) {stack.push(iterator);}public boolean hasNext() {if (stack.empty()) {return false;} else {// 查看栈顶迭代器Iterator<MenuComponent> iterator = stack.peek();if (!iterator.hasNext()) {// 如果栈顶迭代器中已经没有元素,则弹出stack.pop();// 继续查看栈顶return hasNext();} else {return true;}}}public MenuComponent next() {if (hasNext()) {// 查看栈顶迭代器Iterator<MenuComponent> iterator = stack.peek();// 获取当前迭代器中的下一个元素MenuComponent menuComponent = iterator.next();if (menuComponent instanceof Menu) {// 栈顶迭代器中的下一个元素是菜单,则将菜单的Iterator入栈stack.push(((Menu) menuComponent).createIterator());}return menuComponent;} else {return null;}}}MenuItem
实现createIterator()
方法,其他代码不变,仅增加createIterator()
方法123456public class Menu extends MenuComponent {public Iterator<MenuComponent> createIterator() {return new CompositeIterator(menueComponents.iterator());}}Menu
实现createIterator()
方法,其他代码不变,仅增加createIterator()
方法1234567public class MenuItem extends MenuComponent {......public Iterator<MenuComponent> createIterator() {return new NullIterator();}}测试,构造的数据和上面一样,下面仅贴出迭代器使用部分代码
12345678910111213141516public class Test {public static void main(String[] args) {......Iterator<MenuComponent> iterator = allMenus.createIterator();System.out.println("\nVEGETARIAN MENU\n----");while (iterator.hasNext()) {MenuComponent menuComponent = iterator.next();try {if (menuComponent.isVegetarian()) {menuComponent.print();}} catch (UnsupportedOperationException e) {}}}}
组合模式就学到这里了。我们最后来复习下之前学过的几种设计模式的描述:
- 策略模式:封装可互换的行为,并使用委托决定使用哪一个
- 适配器:改变一个或多个类的接口
- 迭代器:提供一个方式来遍历集合,而无需暴露集合的实现
- 外观:简化一群类的接口
- 组合:客户可以将对象的集合以及个别的对象一视同仁
- 观察者:当某个状态改变时,允许一群对象能被通知到