组合模式
面对这样一种问题,可能List里面套List,是一种如下的树形数据结构
简单来说,叶子节点才是实际对象,其他都是一个集合
这时候就需要组合模式
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 想表示对象的部分-整体层次结构(树形结构)。
- 用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
- 树枝和叶子实现统一接口,树枝内部组合该接口。
例子
实现
以下是简化版实现
abstract class MenuComponent {
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int index){
throw new UnsupportedOperationException();
}
public String getName(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
public class MenuItem extends MenuComponent {
String name;
public MenuItem(String name){
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void print() {
System.out.println(getName());
}
}
public class Menu extends MenuComponent {
ArrayList<MenuComponent> menuComponents = new ArrayList<>();
String name;
public Menu(String name){
this.name = name;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return (MenuComponent)menuComponents.get(index);
}
@Override
public String getName() {
return name;
}
@Override
public void print() {
System.out.println();
System.out.println(getName());
System.out.println("---------------------");
Iterator<MenuComponent> iterator = menuComponents.iterator();
while (iterator.hasNext()){
MenuComponent menuComponent = iterator.next();
menuComponent.print();
}
}
}
测试
public class Test {
public static void main(String[] args) {
MenuComponent pancakeMenu = new Menu("PANCAKE MENU");
MenuComponent dinerMenu = new Menu("DINER MENU");
MenuComponent cafeMenu = new Menu("CAFE MENU");
MenuComponent allMenus = new Menu("ALL MENU");
allMenus.add(pancakeMenu);
allMenus.add(dinerMenu);
pancakeMenu.add(new MenuItem("Taco"));
pancakeMenu.add(new MenuItem("Noodles"));
dinerMenu.add(new MenuItem("PIE"));
dinerMenu.add(cafeMenu);
cafeMenu.add(new MenuItem("Black Tea"));
cafeMenu.add(new MenuItem("Green Tea"));
allMenus.print();
}
}
组合迭代器1.0
实现这样一个树形结构
还记得迭代器模式吗?CompositeIterator能够遍历组合中的组件的迭代器
实现
public class CompositeIterator implements Iterator {
Stack stack = new Stack();
public CompositeIterator(Iterator iterator){
stack.push(iterator);
}
@Override
public boolean hasNext() {
if(stack.isEmpty()){
return false;
}else {
Iterator iterator = (Iterator) stack.peek();
if(!iterator.hasNext()){
stack.pop(); // 如果栈顶的迭代器已经结束了,把他出栈,然后重新调用hasNext
return hasNext();
}else {
return true;
}
}
}
@Override
public Object next() {
if(hasNext()){
Iterator iterator = (Iterator) stack.peek();
MenuComponent menuComponent = (MenuComponent) iterator.next();
if(menuComponent instanceof Menu){
stack.push(menuComponent.createIterator());
}
return menuComponent;
}else {
return null;
}
}
}
在抽象类中添加方法
public Iterator createIterator(){
throw new UnsupportedOperationException();
}
在MenuItem中添加方法及空对象
@Override
public Iterator createIterator() {
return new NullIterator();
}
private class NullIterator implements Iterator{
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
return null;
}
}
在Menu中添加方法
@Override
public Iterator createIterator() {
return new CompositeIterator(menuComponents.iterator());
}
测试
public class Test {
public static void main(String[] args) {
MenuComponent pancakeMenu = new Menu("PANCAKE MENU");
MenuComponent dinerMenu = new Menu("DINER MENU");
MenuComponent cafeMenu = new Menu("CAFE MENU");
MenuComponent allMenus = new Menu("ALL MENU");
allMenus.add(pancakeMenu);
allMenus.add(dinerMenu);
pancakeMenu.add(new MenuItem("Taco"));
pancakeMenu.add(new MenuItem("Noodles"));
dinerMenu.add(new MenuItem("PIE"));
dinerMenu.add(cafeMenu);
cafeMenu.add(new MenuItem("Black Tea"));
cafeMenu.add(new MenuItem("Green Tea"));
// allMenus.print();
Iterator<MenuComponent> iterator = allMenus.createIterator();
while (iterator.hasNext()){
MenuComponent component = iterator.next();
System.out.println(component.getName());
}
}
}
可以发现,是深度优先遍历
但是存在重复打印的bug,Black Tea和Green Tea打印了两遍。发现《Head First Design Pattern》书上也是一样的问题
组合迭代器2.0
研究一下问题出现在哪里,发现所有非第一层的叶节点都会出现重复打印
打断点,发现在打印完第一遍Black Tea和Green Tea后,CompositeIterator@986要被删掉了,但是一样的CompositeIterator@991还在,而且cursor=0,说明还会重新遍历一遍。
所以问题所在就是应该是同样的CompositeIterator对象,由于在Menu类中的实现是return new CompositeIterator(menuComponents.iterator());
,所以会创建多个对象。
修改
public class Menu extends MenuComponent {
// 新加这个成员遍历Iterator,来保证每次createIterator获取到的都是同一个对象
Iterator iterator;
// ...
@Override
public Iterator createIterator() {
if(iterator==null){
iterator = new CompositeIterator(menuComponents.iterator());
}
return iterator;
}
}
修改后
由于是同一个对象,cursor已经=2了,所以哪怕再次用到这个迭代器的应用,也因为cursor=size而不会重复打印。
测试
完美解决。
组合迭代器3.0
在这里介绍另外一种修改方法
不做2.0的修改,将CompositeIterator中的代码改为如下
@Override
public Object next() {
if(hasNext()){
Iterator iterator = (Iterator) stack.peek();
MenuComponent menuComponent = (MenuComponent) iterator.next();
if(menuComponent instanceof Menu){
stack.push(((CompositeIterator)menuComponent.createIterator()).stack.peek());
// stack.push(menuComponent.createIterator());
}
return menuComponent;
}else {
return null;
}
}
也就是把stack中的Iterator类型都统一为ArrayListItr的应用,问题也可以解决,在此不多赘述。
其他
通过组合迭代器的方法,可以在MenuComponent中添加一些flag,实现遍历下的筛选(比如筛选出所有饮料,所有价格低于100元的食物等..)