设计模式(二)迭代器模式 & 组合模式
迭代器模式
应用场景
假设有西餐和煎饼两种菜单,需要招待员打印出这两种菜单的所有菜品项目。
方案一
暴露出集合给外部进行遍历
public class MenuItem {
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 String getPrice() { return price; }
public String isVegetarian() { return vegetarian; }
}
public class PancakeHouseMenu1 { //煎饼菜单
ArrayList menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList();
addItem("K&B's Pancake Breakfast","Pancakes with scrambled eggs, and toast",true,2.99);
addItem("Regular Pancake Breakfast","Pancakes with fried eggs, sausage",false,2.99);
addItem("Blueberry Pancakes","Pancakes made with fresh blueberries",true,3.49);
addItem("Waffles","Walfles, with yoiur choice of blueberries or strawberries",true,3.59);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}
public ArrayList getMenuItems() {
return menuItems;
}
}
public class DinerMenu1 { //西餐菜单
static final int MAX_ITEMS = 6;
int numberOfItems;
MenuItem[] menuItems;
public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("Vegetarian BLT","(Fakin') Bacon with lettuce & tomato on whole wheat",true,2.99);
addItem("BLT","Bacon with lettuce & tomato on whole wheat",false,2.99);
addItem("Soup of the day","Soup of the day, with a side of potato salad",false,3.29);
addItem("Hotdog","A hot dog, with saurkraut, relish, onions, topped with cheese",false,3.05);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.err.println("Sorry, menu is full! Can't add item to menu");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems += 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
}
public class Waitress1 { //招待员
public void printMenu() {
PancakeHouseMenu1 pancakeHouseMenu = new PancakeHouseMenu1();
ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();
DinerMenu1 dinerMenu = new DinerMenu1();
MenuItem[] lunchItems = dinerMenu.getMenuItems();
//需要用两个for循环分别遍历两种不同类型的菜单项集合(菜单)
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = (MenuItem)breakfastItems.get(i);
System.out.print(menuItem.getName()+" ");
System.out.println(menuItem.getPrice()+" ");
System.out.println(menuItem.getDescription());
}
for (int j = 0; j < lunchItems.length; j++) {
MenuItem menuItem = lunchItems[j];
System.out.print(menuItem.getName()+" ");
System.out.println(menuItem.getPrice()+" ");
System.out.println(menuItem.getDescription());
}
}
}
从上面的方案一看,招待员的打印菜单实现中带有两次循环,且捆绑于具体的集合类型。
方案二
利用迭代器改进
为了让招待员更方便地遍历菜单,需要定义一个迭代器接口,然后分别实现煎饼和西餐两种具体的迭代器。这样招待员使用迭代器遍历时就可以调用统一的接口而不用关心这两种菜单内的差异处理,循环代码也因此变成只有一个。
public interface Iterator {
boolean hasNext();
Object next();
}
public class DinerMenuIterator2 implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
public Object next() {
MenuItem menuItem = items[position];
position += 1;
return menuItem;
}
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
}
}
}
同理,实现 PancakeHouseMenuIterator2。
public class DinerMenu2 {
//...
public Iterator createIterator() {
return new DinerMenuIterator2(menuItems);
}
}
DinerMenu去掉了getMenuItems方法,因为不再以暴露出内部的实现给外部进行遍历,改为提供封装好遍历逻辑的迭代器给外部直接使用。同理,再为 PancakeHouseMenu2 添加 createIterator 方法。
public class Waitress2 {
PancakeHouseMenu2 pancakeHouseMenu;
DinerMenu2 dinerMenu;
public Waitress(PancakeHouseMenu2 pancakeHouseMenu, DinerMenu2 dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
System.out.println("Menu\n---\nBreakfast");
printMenu(pancakeIterator);
System.out.println("\nLunch");
printMenu(dinerIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName()+", ");
System.out.print(menuItem.getPrice()+" -- ");
System.out.print(menuItem.getDescription());
}
}
}
从上面看,方案二中,招待员仍然捆绑于两个具体的菜单类,我们应该做得更针对接口编程,解耦招待员和具体菜单的关系。
方案三
利用java.util.Iterator
系统本身有迭代器相关接口和已实现的类可直接利用,同时本次抽象出菜单接口供招待员使用,解耦招待员和煎饼西餐两个具体菜单的关系。
public interface Menu {
public Iterator createIterator();
}
public class PancakeHouseMenu3 implements Menu { //煎饼菜单
ArrayList menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList();
addItem("K&B's Pancake Breakfast","Pancakes with scrambled eggs, and toast",true,2.99);
addItem("Regular Pancake Breakfast","Pancakes with fried eggs, sausage",false,2.99);
addItem("Blueberry Pancakes","Pancakes made with fresh blueberries",true,3.49);
addItem("Waffles","Walfles, with yoiur choice of blueberries or strawberries",true,3.59);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}
public Iterator createIterator() {
return menuItems.iterator(); //因ArrayList支持Iterator接口,故直接免去 PancakeHouseMenuIterator3 的实现。
}
}
public class DinerMenu3 implements Menu {
//...
public Iterator createIterator() {
return new DinerMenuIterator3(menuItems); //因数组list(MenuItems[])不支持Iterator接口,故仍需实现 DinerMenuIterator3
}
}
public class DinerMenuIterator3 implements Iterator {
//...
//还需实现 java.util.Iterator 的remove
public void remove() {
if (position <= 0) {
throw new IllegalStateException("You can't remove an item until you've done at least one next()");
}
if (items[position-1] != null) {
for (int i = position-1; i < (items.length-1); i++) {
items[i] = items[i+1];
}
items[items.length-1] = null;
}
}
}
public class Waitress3 {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
}
为了演示Hashtable的Iterator,再添加一个咖啡菜单
public class CafeMenu implements Menu {
Hashtable menuItems = new Hashtable();
public CafeMenu() {}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
public Iterator createIterator() {
return MenuItems.values().iterator();
}
}
再优化一下招待员,使他不用在每次增减菜单时都要改到内部的代码。这也是得益于Menu接口的诞生
public class Waitress4 {
ArrayList menus;
public Waitress(ArrayList menus) {
this.menus = menus;
}
public void printMenu() {
Iterator menuIterator = menus.iterator();
while(menuIterator.hasNext()) {
Menu menu = (Menu)menuIterator.next();
printMenu(menu.createIterator());
}
}
private void printMenu(Iterator iterator) {
//...
}
}
总结
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。把游走的任务放在迭代器上,而不是聚合,简化了聚合的接口和实现,让责任各得其所。
延伸,类的每个责任都有改变的潜在区域,超过一个责任,意味着超过一个改变的区域,应尽量让每个类保存单一责任,即一个类应该只有一个引起变化的原因。
组合模式
应用场景
基于上一节的迭代器模式中的菜单例子,假设要历遍菜单的菜单项中,可能还有子菜单,继续打印包括子菜单里的所有菜单项。
首先定义一个菜单项和子菜单的抽象类,可以作为个别对象和对象组合的父类,使其可被统一的处理。
public abstract class MenuComponent1 {
public void add(MenuComponent1 menuComponent) { throw new UnsupportedOperationException();}
public void remove(MenuComponent1 menuComponent) { throw new UnsupportedOperationException();}
public MenuComponent1 getChild(int i) { throw new UnsupportedOperationException();}
public String getName() { throw new UnsupportedOperationException();}
public String getDescription() { throw new UnsupportedOperationException();}
public String getPrice() { throw new UnsupportedOperationException();}
public String isVegetarian() { throw new UnsupportedOperationException();}
public void print() { throw new UnsupportedOperationException();}
}
public class MenuItem1 extends MenuComponent1 {
//...
public void print() {
System.out.print(" "+getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.print(", "+getPrice());
System.out.print(" -- "+getDescription());
}
}
public class Menu1 extends MenuComponent1 {
ArrayList menuComponents = new ArrayList();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
public void add(MenuComponent1 menuComponent) { menuComponent.add(menuComponent);}
public void remove(MenuComponent1 menuComponent) { menuComponent.remove(menuComponent);}
public MenuComponent1 getChild(int i) { return (MenuComponent)menuComponent.get(i);}
public String getName() { return name; }
public String getDescription() { return description; }
public void print() {
System.out.print("\n "+getName());
System.out.print(", "+getDescription());
System.out.print("----------------");
Iterator iterator = menuComponents.iterator();
while(iterator.hasNext()) {
MenuComponent1 menuComponent = (MenuComponent)iterator.next();
menuComponent.print(); //如果元素是(子)菜单,则产生递归效果
}
}
}
public class Waitress {
MenuComponent1 allMenus;
public Waitress(MenuComponent1 allMenus) { this.allMenus = allMenus; }
public void printMenu() { allMenus.print(); }
}
public class MenuTestDrive {
public static void main(String args[]) {
MenuComponent1 pancakeHouseMenu = new Menu1("Pancake House Menu","Breakfast");
MenuComponent1 dinerMenu = new Menu1("Diner Menu","Lunch");
MenuComponent1 cafeMenu = new Menu1("Cafe Menu","Dinner");
MenuComponent1 dessertMenu = new Menu1("Dessert Menu","Dessert of course");
MenuComponent1 allMenus = new Menu1("All Menus","All menus combined");
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
dinerMenu.add(new MenuItem1("Pasta","Spaghetti with Marinara Sauce, and a slice of sourdough bread",true,3.89));
dinerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem1("Apple Pie","Apple pie with a flakey crust, topped with vanilla ice cream",true,1.59));
Waitress waitress = new Waitress((allMenus));
waitress.printMenu();
}
}
上面使用了树状结构设计菜单组件MenuComponent,Menu类是组件的其中一个具体实现,并可以往上面添加菜单组件MenuComponent,实现嵌套存储。
上面的实现中没有提供遍历的元素,如果要提供出来则需要做一套这个树的迭代器。
假设现在要找出所有素食菜品,做一下改造
public abstract class MenuComponent2 {
//...
public Iterator createIterator() {};
}
public class Menu2 extends MenuComponent2 {
//...
public Iterator createIterator() {
return new CompositeIterator(menuComponents.iterator());
};
}
public class MenuItem2 extends MenuComponent2 {
//...
public Iterator createIterator() {
return new NullIterator(); // 为了保持一致性和安全性,提供空迭代器给外部遍历时进行假调用(因为外部只认菜单组件的接口,而不会理会具体实现是否支持)
};
}
public class NullIterator implements Iterator {
public Object next() {
return null;
}
public boolean hasNext() {
return false;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
//树(组合)迭代器
public class CompositeIterator implements Iterator {
Stack stack = new Stack(); //用来存放整个组合里面的所有迭代器
public CompositeIterator(Iterator iterator) {
stack.push(iterator);
}
public Object next() {
if (hasNext()) {
Iterator iterator = (Iterator) stack.peek(); //使用栈顶的迭代器历遍,一个迭代器历遍完后hasNext内会自动更新栈顶的迭代器
MenuComponent component = (MenuComponent) iterator.next();
if (component instanceof Menu) {
stack.push(component.createIterator()); //将子菜单的迭代器推入栈
}
return component;
} else {
return null;
}
}
public boolean hasNext() {
if (stack.empty()) {
return false;
} else {
Iterator iterator = (Iterator) stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext(); //递归
} else {
return true;
}
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public class Waitress2 {
//...
public void printVegetarianMenu() {
Iterator iterator = allMenus.createIterator();
while(iterator.hasNext()) {
MenuComponent2 menuComponent2 = (MenuComponent2)iterator.next();
try {
if (menuComponent2.isVegetarian()) {
menuComponent2.print();
}
} catch (UnsupportedOperationException e) { }
}
}
}
总结
组合模式允许将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。使用组合结构,我们能把相同的操作应用在组合和个别对象上,即忽略对象组合和个别对象之间的差别。
组合模式以单一责任设计原则换取了透明性,但失去了相对的安全性,即是调用者可能会对一个元素做出不恰当或没意义的操作方法调用,这个是典型的折衷案例。
延伸:组件还可以有指向父节点的指针,方便反向游走;有顺序的子节点,增删节点方法需额外设计;缓存起需要历遍计算的结果,节省开支。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 mingfungliu@gmail.com
文章标题:设计模式(二)迭代器模式 & 组合模式
文章字数:2.7k
本文作者:Mingfung
发布时间:2019-02-11, 09:59:00
最后更新:2019-02-12, 14:33:45
原始链接:http://blog.ifungfay.com/设计模式/设计模式(二)迭代器模式 & 组合模式/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。