Head First设计模式读书笔记八 第九章下 组合模式

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u011109881/article/details/82730793

之前的总结链接:
https://blog.csdn.net/u011109881/article/details/58710579
对比headFirst书中的例子,我觉得书中的组合模式的例子比上面的例子更好一些。上面的例子虽然通俗易懂,但是总感觉不够深入,也不够完善。

组合模式+迭代器模式

接着上一节最后的例子,例子的最终list结构图是这样的:
这里写图片描述
若要给DinerMenu新加一种Menu(即下面这样),则需要对现有结构进行较大改动。

这里写图片描述
可以看到,目前的结构中分为两种结构,一种是menu,是一种容器,可以包含菜单项,而第二种是menuItem,是一个节点,只包含该菜单项的相关信息。
如果把这种关系推广到树形结构,那么menu则相当于非叶子节点,而menu则相当于叶子节点。从上图也很容易看出来。
运用组合模式,则可以实现上面的这种树形结构,并且方便我们的遍历。

组合模式示例

示例基于上一节最后的代码改进
以MenuComponent为基类

public abstract class MenuComponent {
	//適用于菜單項+菜單容器
	public String getName() {
		throw new UnsupportedOperationException();
	}
	//適用于菜單項+菜單容器
	public String getDescription() {
		throw new UnsupportedOperationException();
	}
	//適用于菜單項
	public boolean isVegetarian() {
		throw new UnsupportedOperationException();
	}
	//適用于菜單項
	public double getPrice() {
		throw new UnsupportedOperationException();
	}
	//適用于菜單容器
	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 void print() {
		throw new UnsupportedOperationException();
	}
}

menu和menuItem继承之

public class Menu extends MenuComponent {
	String name;
	String description;
	ArrayList<MenuComponent> menuItems;

	public Menu(String name, String description) {
		menuItems = new ArrayList<MenuComponent>();
		this.name = name;
		this.description = description;
	}

	@Override
	public void add(MenuComponent menuComponent) {
		menuItems.add(menuComponent);
	}

	@Override
	public void remove(MenuComponent menuComponent) {
		menuItems.remove(menuComponent);
	}

	@Override
	public MenuComponent getChild(int i) {
		return menuItems.get(i);
	}

	@Override
	public String getName() {
		return this.name;
	}

	@Override
	public String getDescription() {
		return this.description;
	}

	@Override
	public void print() {
		System.out.println(" " + getName());
		System.out.println(" "  + getDescription());
		System.out.println(" ------------------------------------ ");
		Iterator<MenuComponent> iterator = menuItems.iterator();
		//重点 递归调用
		while (iterator.hasNext()) {
			MenuComponent menuComponent = iterator.next();
			menuComponent.print();
		}
	}

}

关键在于这里的递归,利用iterator 遍历Menu中的子项,注意这些子项既可能是Menu也可能时MenuItem。如果时MenuItem,则调用的是MenuItem的print方法,如果是Menu,则会调用Menu的print,此时则会进入菜单的第一次递归调用。这里其实运用到多态的思想。

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;
		this.vegetarian = vegetarian;
		this.price = price;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public boolean isVegetarian() {
		return vegetarian;
	}

	@Override
	public double getPrice() {
		return price;
	}

	@Override
	public void print() {
		System.out.println(" " + getName());
		System.out.println("isVegetarian " + isVegetarian());

		System.out.println(" " + getPrice());
		System.out.println(" --" + getDescription());
	}

	@Override
	public String toString() {
		return "name =" + name + " description " + description
				+ " isVegetarian " + vegetarian + " price= " + price;
	}
}

改进Waitress

public class Waitress {
	MenuComponent allMenus;

	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}

	public void printMenu() {
		allMenus.print();
	}
}

DinerMenu和PancakeHouseMenu没有存在必要了,改写测试类

public class Printer2 {

	public static void main(String[] args) {
		MenuComponent breakfasetMenu = new Menu("PANCAKE HOUSE MENU", "breakfast");
		MenuComponent lunchMenu = new Menu("LUNCH MENU", "lunch");
		MenuComponent dinerMenu = new Menu("DINER MENU", "diner");
		
		MenuComponent allMenuComponent = new Menu("ALL MENUS","All menus combined");
		allMenuComponent.add(dinerMenu);
		allMenuComponent.add(lunchMenu);
		allMenuComponent.add(breakfasetMenu);
		dinerMenu.add(new MenuItem("diner menu item1", "dinermenu item1", true, 1.23));
		dinerMenu.add(new MenuItem("diner menu item2", "dinermenu item2", false, 1.43));
		dinerMenu.add(new MenuItem("diner menu item3", "dinermenu item3", true, 3.23));
		lunchMenu.add(new MenuItem("lunchMenu menu item1", "lunchMenu item1", false, 1.434));
		lunchMenu.add(new MenuItem("lunchMenu menu item2", "lunchMenu item2", true, 6.5));
		breakfasetMenu.add(new MenuItem("breakfasetMenu menu item1", "breakfasetMenu item1", true, 1.2));
		breakfasetMenu.add(new MenuItem("breakfasetMenu menu item2", "breakfasetMenu item2", false, 1.12));
		
		Waitress waitress = new Waitress(allMenuComponent);
		waitress.printMenu();
		
	}

}

测试结果:

 ALL MENUS
 All menus combined
 ------------------------------------ 
 DINER MENU
 diner
 ------------------------------------ 
 diner menu item1
isVegetarian true
 1.23
 --dinermenu item1
 diner menu item2
isVegetarian false
 1.43
 --dinermenu item2
 diner menu item3
isVegetarian true
 3.23
 --dinermenu item3
 LUNCH MENU
 lunch
 ------------------------------------ 
 lunchMenu menu item1
isVegetarian false
 1.434
 --lunchMenu item1
 lunchMenu menu item2
isVegetarian true
 6.5
 --lunchMenu item2
 PANCAKE HOUSE MENU
 breakfast
 ------------------------------------ 
 breakfasetMenu menu item1
isVegetarian true
 1.2
 --breakfasetMenu item1
 breakfasetMenu menu item2
isVegetarian false
 1.12
 --breakfasetMenu item2

完成时的类结构:
这里写图片描述
可以看到,在组合模式中,Menu和MenuItem都是一个个节点,都可以参与遍历。注意:Waitess其实一个Menu节点,为了与上面的例子结合起来看,我将他写成了Waitress,其实重点应该在Waitress中包含的allMenus实例。
组合模式的思想

关键点:继承同一个抽象类。
可以看到Menu和MenuItem都继承了抽象父类MenuComponent,这样做
可以忽略容器和叶子节点的节点的区别。至于为什么这么做,是方便菜单项和子项的统一遍历(不需要区分是Menu还是MenuItem----多态的功劳)
试想一下,如果只有Menu的话,遍历顺序是什么样的呢?从上面的输出结果,不难想象,这种遍历,与二叉树的先序遍历极为类似。
至于MenuItem则可以想象为不能拥有子节点的Menu。
以上总结为两点:
1.组合模式如何实现:容器(MENU)与节点(MENUITEM)继承同一个接口
2.组合模式目的:实现更简洁的遍历

等一下,MenuComponent似乎违背了单一职责原则?
通常,我们推荐一个类只干一件事,但是在MenuComponent似乎既干了菜单的事情也干了菜单项的事情,如何解释呢?
没错,组合模式的确违反了单一职责原则,但是这是有目的的。
组合模式通过这种方式让容器节点和叶子节点一视同仁的遍历和操作,用户不需要知道他操作的是容器节点还是叶子节点。
如果我们不采用这种方式,那么客户势必需要使用instanceof来判断、分别处理叶子和容器节点,这样透明性变差(用户需要知道节点的内部结构)。因此这里的操作是牺牲单一职责换取透明性(用户不需要知道他操作的是容器节点还是叶子节点)。
在日常开发中,类似的权衡处理方式是时间换空间或者空间换时间的算法,这样应该更容易理解吧?

组合模式终极版:组合(外部)迭代器

1.改进MenuComponent,添加新方法

	//新增方法 适用于菜单+菜单项
	public Iterator<MenuComponent> createIterator(){
		throw new UnsupportedOperationException();
	}

2.为Menu和MenuItem各自实现该方法
Menu:

	@Override
	public Iterator<MenuComponent> createIterator() {
		return menuItems.iterator();
	}

MenuItem:

	@Override
	public Iterator<MenuComponent> createIterator() {
		return new NullIterator();
	}

3.NullIterator:(空迭代器)

public class NullIterator implements Iterator<MenuComponent> {

	public boolean hasNext() {
		return false;
	}

	public MenuComponent next() {
		return null;
	}

}

4.组合迭代器登场

public class CompositeIterator implements Iterator<MenuComponent> {

	Stack<Iterator<MenuComponent>> stack = new Stack<Iterator<MenuComponent>>();//存储iterator的栈

	public CompositeIterator(Iterator<MenuComponent> iterator) {
		stack.push(iterator);
	}

	public boolean hasNext() {
		if (stack.isEmpty()) {
			// if stack is null, does not have next
			return false;
		} else {
			// get the top of stack
			Iterator<MenuComponent> iterator = stack.peek();
			if (!iterator.hasNext()) {
				//如果栈顶iterator没有元素 弹出该iterator,递归判断是否hasNext
				if(iterator instanceof CompositeIterator){
					System.out.println("pop>>>>>>>>>>>>>>>>>>>"+((CompositeIterator)stack.pop()));
				}else{
					System.out.println("pop>>>>>>>>>>>>>>>>>>>"+iterator.getClass());
					stack.pop();
				}
				
				return hasNext();
			} else {
				//栈顶iterator有next元素,返回true
				return true;
			}
		}
	}

	public MenuComponent next() {
		System.out.println("xxxxx");
			//取出栈顶iterator
			Iterator<MenuComponent> iterator = stack.peek();
			//取出栈顶iterator的next
			MenuComponent menuComponent = iterator.next();
			System.out.println("hasNEx");
			if (menuComponent instanceof Menu) {
				//如果是Menu类型 还需要放入栈内 为了后面的遍历
				System.out.println("<<<<<<<<<<<<<<<<<<<<<push"+menuComponent.getName()+" "+stack.push(menuComponent.createIterator()));
			}
			//不管是Menu还是MenuItem都返回
			return menuComponent;
	}

}

5.当然侍者类也要改进

public class Waitress {
	MenuComponent allMenus;

	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}

	public void printMenu() {
		allMenus.print();
	}
	public void printVegetarianMenu(){
		//得到allMenu的Iterator 其类型为CompositeIterator(所有Menu的createIterator返回的实际实例类型为CompositeIterator)
		//遍历之得到 存储在 allMenus的MenuComponent
		Iterator<MenuComponent> iterator = allMenus.createIterator();
		System.out.println(" printVegetarianMenu  ");
		while(iterator.hasNext()){//调用的CompositeIterator的hasNext
			MenuComponent menuComponent =iterator.next();
			System.out.println("for next....");
			try{
				if(menuComponent.isVegetarian()){
					menuComponent.print();
				}
			}catch(UnsupportedOperationException e){
				System.err.println("Not a menuItem");
			}
		}
	}
}
/**
遍历过程:
1.对allMenus遍历 将breakfasetMenu和lunchMenu放入stack                              
2.对breakfasetMenu进行遍历,判断其子元素是否是蔬菜,遍历完毕,从stack弹出breakfasetMenu
3.对lunchMenu进行遍历,判断子元素是否是蔬菜,遇到dinerMenu时放入stack                        
4.对dinerMenu进行遍历,判断子元素是否是蔬菜,遍历完毕,弹出dinerMenu
5.对lunchMenu遍历完毕,弹出lunchMenu.
6.对allMenus遍历完毕,弹出allMenus,遍历终了
 */

遍历图:
在这里插入图片描述
不过dinner貌似会被push两次,所以会输出两次。看来几遍没找到原因。。以后再看看吧。。
log:

 printVegetarianMenu  
Not a menuItem
<<<<<<<<<<<<<<<<<<<<<pushPANCAKE HOUSE MENU iterator.CompositeIterator@15db9742
for next....
for next....
 ====================
 breakfasetMenu menu item1
isVegetarian true
 1.2
 breakfasetMenu item1
 ====================
for next....
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@15db9742
<<<<<<<<<<<<<<<<<<<<<pushLUNCH MENU iterator.CompositeIterator@6d06d69c
for next....
Not a menuItem
for next....
<<<<<<<<<<<<<<<<<<<<<pushDINER MENU iterator.CompositeIterator@7852e922
<<<<<<<<<<<<<<<<<<<<<pushDINER MENU iterator.CompositeIterator@4e25154f
for next....
Not a menuItem
for next....
 ====================
 diner menu item1
isVegetarian true
 1.23
 dinermenu item1
 ====================
for next....
for next....
 ====================
 diner menu item3
isVegetarian true
 3.23
 dinermenu item3
 ====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@4e25154f
for next....
 ====================
 diner menu item1
isVegetarian true
 1.23
 dinermenu item1
 ====================
for next....
for next....
 ====================
 diner menu item3
isVegetarian true
 3.23
 dinermenu item3
 ====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@7852e922
for next....
 ====================
 lunchMenu menu item2
isVegetarian true
 6.5
 lunchMenu item2
 ====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@6d06d69c
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr

这个最后的迭代器稍显复杂,其目的是为了构建一个外部迭代器,但是个人觉得上一个内部迭代器完全够用了,毕竟对于大多数集合类,有已经支持增强for循环了,这个可用可以不用Iterator似乎没那么大作用了。至于这个push两次的问题,有时间再看看吧。。。

猜你喜欢

转载自blog.csdn.net/u011109881/article/details/82730793