为了更好的浏览体验,请不要在本页面禁用 Javascript
2020-05-18
 Tags: 

Composite模式实现及与Iterator模式结合

实现
基于前文Composite模式,实现简单Demo
关注的问题
保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构的上移和组件的删除。通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作。
Composte模式的目的之一是使得用户不知道他们正在使用的具体的Leaf和Composite类。因此,Component类应为Leaf和Composite类尽可能多定义一些公共操作。Component类通常为这些操作提供缺省的实现,而Leaf和Composite子类可以对它们进行重定义。
声明管理子部件的操作。虽然C omposite类实现了Add和Remove操作用于管理子部件,但在Composite模式中一个重要的问题是:在 Composite类层次结构中哪一些类声明这些操作。是应该在Component中声明这些操作,并使这些操作对 Leaf类有意义呢,还是只应该在Composite和它的子类中声明并定义这些操作呢?
存贮组件最好用哪一种数据结构? Composite可使用多种数据结构存贮它们的子节点,包括连接列表、树、数组和hash表。数据结构的选择取决于效率。
透明组合模式 和 安全组合模式 都有各自的优点和缺点,应该优先选择哪一种呢?透明组合模式 将公共接口封装到抽象根节点(Component)中,那么系统所有节点就具备一致行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用 透明组合模式 也许会更好(代价:为剩下少数层次节点引入不需要的方法);而如果当系统各个层次差异性行为较多或者树节点层次相对稳定(健壮)时,采用安全组合模式
实例
场景
针对上一文中的iterator模式的餐馆合并实例,我们提出新的需求:
给正餐菜单增加“甜点”子菜单。如图,让甜点菜单变成正餐菜单中的一个子节点,我们想要的类似下图。但很明显,iterator模式的设计无法满足:
我们需要某种树状结构来容纳嵌套菜单和菜单项,因此我们需要改用Composite模式来进行设计。
代码
根据前面的模式结构,并结合上图:
首先设计MenuComponent抽象构件类,里面定义组合菜单和菜单项的共有方法
package CompositeUtils; /** * Component抽象类 为组合中的所有对象定义一个接口 组合菜单(CompositeMenu)和叶子菜单项(MenuItem)继承它 */ public abstract class MenuComponent { // =======菜单(组合节点)的方法======= /** * 增加菜单项 */ public void add(MenuComponent component) { throw new UnsupportedOperationException(); } /** * 删除指定菜单项 */ public void remove(MenuComponent component) { throw new UnsupportedOperationException(); } /** * 获取指定下标的菜单项 */ public MenuComponent get(int i) { throw new UnsupportedOperationException(); } // =======菜单项(叶子节点)的方法======= /** * 获取菜单项的名称 */ public String getName() { throw new UnsupportedOperationException(); } /** * 获取菜单项的价格 */ public double getPrice() { throw new UnsupportedOperationException(); } /** * 打印出菜单中的所有菜单项的信息,或者单个菜单项的信息 菜单和菜单项都要用到的方法 */ public void print() { } } /** * 同时将菜单项和菜单的方法定义了出来,继承时,菜单项和菜单只需要覆盖各自的实现即可。 * 这虽然违背了“单一责任”设计原则,但在具体场景下,为了需求和设计,需要采用折中的方式。 */
Java
后设计CompositeMenuMenuItem来继承MenuComponent抽象类,这里选用透明式组合方式,即,我们仅在树枝结构中实现管理子节点的相关方法。
package CompositeUtils; import java.util.ArrayList; import java.util.List; public class CompositeMenu extends MenuComponent { List<MenuComponent> menuComponents = new ArrayList<>(); String name; String description; public CompositeMenu() { } public CompositeMenu(String name, String description) { this.name = name; this.description = description; } @Override public void add(MenuComponent component) { menuComponents.add(component); } @Override public void remove(MenuComponent component) { menuComponents.remove(component); } @Override public MenuComponent get(int i) { return menuComponents.get(i); } @Override public void print() { System.out.println("菜单名称:" + getName() + "(" + getDescription() + ")"); for (MenuComponent menuComponent : menuComponents) { menuComponent.print(); } } @Override public String getName() { return this.name; } public String getDescription() { return description; } }
Java
package CompositeUtils; public class MenuItem extends MenuComponent { String name; double price; public MenuItem() { } public MenuItem(String name, double price) { this.name = name; this.price = price; } @Override public String getName() { return name; } @Override public double getPrice() { return price; } @Override public void print() { System.out.println(name + ":" + price); } public void setName(String name) { this.name = name; } public void setPrice(double price) { this.price = price; } }
Java
最后定义服务员类Waitress类,并在main中调用执行
package CompositeUtils; public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; } /** * 打印出菜单 */ public void printMenu() { allMenus.print(); } }
Java
import CompositeUtils.MenuComponent; import CompositeUtils.MenuItem; import CompositeUtils.Waitress; import CompositeUtils.CompositeMenu; public class Main2TestComposite { public static void main(String[]args) { //建造烧饼菜单 MenuComponent pancakeMenu = new CompositeMenu("早餐","原烧饼店菜单"); pancakeMenu.add(new MenuItem("葱花饼", 3.5)); pancakeMenu.add(new MenuItem("牛肉饼", 6)); pancakeMenu.add(new MenuItem("猪肉饼", 5)); pancakeMenu.add(new MenuItem("韭菜饼", 2.5)); //建造正餐菜单子菜单-甜点菜单 MenuComponent dessertMenu = new CompositeMenu("甜点","正餐子菜单"); dessertMenu.add(new MenuItem("芒果牛奶冰", 8)); dessertMenu.add(new MenuItem("珍珠果豆花", 6)); dessertMenu.add(new MenuItem("榴莲蛋黄", 8)); //建造正餐菜单 MenuComponent dinnerMenu = new CompositeMenu("正餐","原饭店菜单"); dinnerMenu.add(new MenuItem("青椒肉丝炒饭", 12)); dinnerMenu.add(new MenuItem("番茄鸡蛋炒饭", 10)); dinnerMenu.add(new MenuItem("鱼香茄子炒饭", 11)); dinnerMenu.add(new MenuItem("土豆烧鸡炒饭", 12.5)); dinnerMenu.add(dessertMenu); //建造主菜单 MenuComponent totalMenu = new CompositeMenu("菜单", "总菜单"); totalMenu.add(pancakeMenu); totalMenu.add(dinnerMenu); //服务员打印菜单 Waitress waitress = new Waitress(totalMenu); System.out.println("\ncomposite模式:\n"); waitress.printMenu();//递归调用菜单项和菜单内部的print,是内部迭代 } }
Java
运行结果
composite模式: 菜单名称:菜单(总菜单) 菜单名称:早餐(原烧饼店菜单) 葱花饼:3.5 牛肉饼:6.0 猪肉饼:5.0 韭菜饼:2.5 菜单名称:正餐(原饭店菜单) 青椒肉丝炒饭:12.0 番茄鸡蛋炒饭:10.0 鱼香茄子炒饭:11.0 土豆烧鸡炒饭:12.5 菜单名称:甜点(正餐子菜单) 芒果牛奶冰:8.0 珍珠果豆花:6.0 榴莲蛋黄:8.0
Java
与Iterator的结合
在新需求下,我们改用了Composite模式,然而问题来了:
1.
iterator模式的好处就烟消云散了,因为在本例中,Composite模式下对菜单的存储是通过ArrayList这一数据结构进行的。
2.
并且,例子中使用的迭代都是递归调用的菜单项和菜单内部迭代的方式,属于内部迭代,如果我们需要外部迭代该怎么办?
首先我们来解决问题2:
加入有新需求:一位顾客只想看只含有蔬菜性质的菜单。如何实现?
step1: 由于MenuComponent抽象类中定义了所有对象的方法,我们现在该类中添加判断食品的方法,然后再在叶节点和树枝节点中进行重写
public abstract class MenuComponent { // =======菜单项(叶子节点)的增加方法======= /** * 判断是否为蔬菜类食品 */ public boolean isVegetable() { throw new UnsupportedOperationException(); } }
Java
step2-3: 在菜单项MenuItem中重写相关方法,注意,可能还需要定义新的变量和构造函数
public class MenuItem extends MenuComponent { // 新增蔬菜类食品标志 boolean isVegetable; public MenuItem(String name, double price) { this.name = name; this.price = price; // 修改构造函数 this.isVegetable = false; } // 新增构造函数 public MenuItem(String name, double price, boolean isVegetable) { this.name = name; this.price = price; this.isVegetable = isVegetable; } // =======菜单项(叶子节点)的增加方法======= /** * 重写抽象类的方法 */ @Override public boolean isVegetable() { return isVegetable; } public void setVegetable(boolean isVegetable) { this.isVegetable = isVegetable; }
Java
step4: 然后为了迭代组合中的每个对象,来调用isVegetable(),我们需要设计一个新的组合菜单迭代器CompositeIterator。注意,这个迭代器中使用了递归的算法,可以递归的迭代树枝节点。
package CompostiteWithItrUtils; import java.util.ArrayDeque; import java.util.Deque; import ItrUtils.MyIterator; /** * 组合菜单迭代器 * 4.使用递归创建组合菜单的迭代器 */ public class CompositeIterator implements MyIterator<MenuComponent> { /** 迭代器栈,存储最近一次迭代的菜单的迭代器 */ Deque<MyIterator<MenuComponent>> stack = new ArrayDeque<MyIterator<MenuComponent>>(); public CompositeIterator() { } public CompositeIterator(MyIterator<MenuComponent> myIterator) { // 初始化时中将组合菜单的总迭代器(即List<MenuComponent>的迭代器)放入栈 stack.push(myIterator); } @Override public boolean hasNext() { if (stack.isEmpty()) return false; // 返回栈顶的迭代器 MyIterator<MenuComponent> myIterator = stack.peek(); // 如果该迭代器仍有元素可迭代,返回true if (myIterator.hasNext()) { return true; } // 如果迭代器元素都已迭代完,弹出栈,递归判断栈中下一个迭代器 else { stack.pop(); return hasNext(); } } @Override public MenuComponent next() { // 如果没有元素可迭代,直接返回null if (!hasNext()) return null; // 有元素可迭代时,返回栈顶迭代器 MyIterator<MenuComponent> iterator = stack.peek(); // 取出迭代器中的下一个元素 MenuComponent component = iterator.next(); // 如果该元素为菜单,将子菜单的迭代器放入栈中。这样下一次执行next()方法,则迭代该子菜单了 if (component instanceof CompositeMenu) { stack.push(component.createIterator()); } return component; } }
Java
step5-8: 接着在MenuComponent抽象类中添加创建迭代器的抽象方法,并在叶节点和树枝节点中进行重写。注意:叶节点没有子节点,所有创建的迭代器为空,所以需要定义一个空迭代器。
public abstract class MenuComponent { /** * 创建迭代器 */ public abstract MyIterator<MenuComponent> createIterator(); }
Java
空迭代器的定义
/** * 空迭代器 */ public class NullIterator implements MyIterator<MenuComponent> { @Override public boolean hasNext() { return false; } @Override public MenuComponent next() { return null; } }
Java
菜单项中重写
public class MenuItem extends MenuComponent { /** * 菜单项创建迭代器 * @return NullIterator */ @Override public MyIterator<MenuComponent> createIterator() { return new NullIterator(); } }
Java
组合菜单中重写
public class CompositeMenu extends MenuComponent { /** * 组合菜单创建迭代器 */ @Override public MyIterator<MenuComponent> createIterator() { var myIterator=new GetMyItrFromArrayList(menuComponents); return new CompositeIterator(myIterator); } }
Java
这里的GetMyItrFromArrayList类是从ArrayList中返回MyIterator类型的迭代器,类似iterator模式一文中PancakeMenuIterator的操作(本质上相同)
package CompostiteWithItrUtils; import java.util.ArrayList; import ItrUtils.MyIterator; /** * 从ArrayList对象中获取MyIterator类型的迭代器 */ public class GetMyItrFromArrayList implements MyIterator<MenuComponent>{ ArrayList<MenuComponent> menuItems; int position = 0; public GetMyItrFromArrayList(ArrayList<MenuComponent> menuItems) { this.menuItems = menuItems; } @Override public boolean hasNext() { if (position >= menuItems.size() || menuItems.get(position) == null) return false; return true; } @Override public MenuComponent next() { MenuComponent item = menuItems.get(position); position += 1; return item; } }
Java
step9: 在服务员类中添加新需求的业务处理函数printVegetarianMenu()
public class Waitress { /** * 打印蔬菜食品 */ public void printVegetarianMenu() { MyIterator<MenuComponent> iterator = allMenus.createIterator(); while (iterator.hasNext()) { MenuComponent component = iterator.next(); try { if (component.isVegetable()) System.out.println(component.getName()); } // 如果为组合菜单调用isVegetable方法,会直接抛出异常,这里会直接不处理 catch (UnsupportedOperationException e) { ; } } } }
Java
然后再在main函数中进行测试
public class Main2TestCombine { public static void main(String[] args) { //建造烧饼菜单 MenuComponent pancakeMenu = new CompositeMenu("早餐","原烧饼店菜单"); pancakeMenu.add(new MenuItem("葱花饼", 3.5,true)); pancakeMenu.add(new MenuItem("牛肉饼", 6,false)); pancakeMenu.add(new MenuItem("猪肉饼", 5,false)); pancakeMenu.add(new MenuItem("韭菜饼", 2.5,true)); //建造正餐菜单子菜单-甜点菜单 MenuComponent dessertMenu = new CompositeMenu("甜点","正餐子菜单"); dessertMenu.add(new MenuItem("芒果牛奶冰", 8,false)); dessertMenu.add(new MenuItem("珍珠果豆花", 6,false)); dessertMenu.add(new MenuItem("榴莲蛋黄", 8,false)); //建造正餐菜单 MenuComponent dinnerMenu = new CompositeMenu("正餐","原饭店菜单"); dinnerMenu.add(new MenuItem("青椒肉丝炒饭", 12,false)); dinnerMenu.add(new MenuItem("番茄鸡蛋炒饭", 10,true)); dinnerMenu.add(new MenuItem("鱼香茄子炒饭", 11,false)); dinnerMenu.add(new MenuItem("土豆烧鸡炒饭", 12.5,false)); dinnerMenu.add(dessertMenu); //建造主菜单 MenuComponent totalMenu = new CompositeMenu("菜单", "总菜单"); totalMenu.add(pancakeMenu); totalMenu.add(dinnerMenu); //服务员打印菜单 Waitress waitress = new Waitress(totalMenu); System.out.println("\ncomposite+iterator模式:\n"); // waitress.printMenu(); waitress.printVegetarianMenu(); } }
Java
结果
composite+iterator模式: 葱花饼 韭菜饼 番茄鸡蛋炒饭
Java
然后解决问题1:
注意到前面到例子中,创建组合菜单是通过新建CompositeMenu类,而在该类的内部是采用的ArrayList这一数据结构,而在iterator模式一文的例子中,午餐餐馆的菜单是通过数组实现的,为了继续使用数组实现,我们将CompositeMenu拷贝一份为CompositeMenu_ArrayVer,并修改相关实现细节
public class CompositeMenu_ArrayVer extends MenuComponent { MenuComponent[] menuComponents = new MenuComponent[20]; int numberOfItems = 0; String name; String description; public CompositeMenu_ArrayVer() { } public CompositeMenu_ArrayVer(String name, String description) { this.name = name; this.description = description; } @Override public void add(MenuComponent component) { if (numberOfItems >= menuComponents.length) { System.out.println("菜单已满,不能再添加!"); return; } if (component instanceof MenuItem) { var menuItem = new MenuItem(component.getName(), component.getPrice(), component.isVegetable()); menuComponents[numberOfItems++] = menuItem; }else{ menuComponents[numberOfItems++] = component; } } @Override public void remove(MenuComponent component) { // 这里对数组元素的删除相当于后一个覆盖前一个 for (int i = 0; i < numberOfItems; i++) { if (menuComponents[i].getName().equals(component.getName())) { for (int j = i; j + 1 < numberOfItems; j++) { menuComponents[j] = menuComponents[j + 1]; } menuComponents[--numberOfItems] = null; break; } } } @Override public MenuComponent get(int i) { return menuComponents[i]; } @Override public void print() { System.out.println("\n菜单名称:" + getName() + "(" + getDescription() + ")"); for (MenuComponent menuComponent : menuComponents) { if(menuComponent==null)break; menuComponent.print(); } } @Override public String getName() { return this.name; } public String getDescription() { return description; } /** * 组合菜单创建迭代器 */ @Override public MyIterator<MenuComponent> createIterator() { var myIterator = new GetMyItrFromArray(menuComponents); return new CompositeIterator(myIterator); } }
Java
注意到CompositeMenu_ArrayVer中的createIterator方法从数组对象中返回一个迭代器,因此参考iterator模式一文中的PancakeMenuIterator,拷贝一份为GetMyItrFromArray
/** * 从对象数组中获取MyIterator类型的迭代器 */ public class GetMyItrFromArray implements MyIterator<MenuComponent> { MenuComponent[] menuComponents; int position = 0; public GetMyItrFromArray(MenuComponent[] menuComponents) { this.menuComponents = menuComponents; } @Override public boolean hasNext() { if (position >= menuComponents.length || menuComponents[position] == null) return false; return true; } @Override public MenuComponent next() { MenuComponent item = menuComponents[position]; position += 1; return item; } }
Java
修改CompositeIterator对新的CompositeMenu_ArrayVer聚合对象做适配
public class CompositeIterator implements MyIterator<MenuComponent> { /** 迭代器栈,存储最近一次迭代的菜单的迭代器 */ Deque<MyIterator<MenuComponent>> stack = new ArrayDeque<MyIterator<MenuComponent>>(); public CompositeIterator() { } public CompositeIterator(MyIterator<MenuComponent> myIterator) { // 初始化时中将组合菜单的总迭代器(即List<MenuComponent>的迭代器)放入栈 stack.push(myIterator); } @Override public boolean hasNext() { if (stack.isEmpty()) return false; // 返回栈顶的迭代器 MyIterator<MenuComponent> myIterator = stack.peek(); // 如果该迭代器仍有元素可迭代,返回true if (myIterator.hasNext()) { return true; } else {// 如果迭代器元素都已迭代完,弹出栈,递归判断栈中下一个迭代器 stack.pop(); return hasNext(); } } @Override public MenuComponent next() { // 如果没有元素可迭代,直接返回null if (!hasNext()) return null; // 有元素可迭代时,返回栈顶迭代器 MyIterator<MenuComponent> iterator = stack.peek(); // 取出迭代器中的下一个元素 MenuComponent component = iterator.next(); // 如果该元素为菜单,将子菜单的迭代器放入栈中。这样下一次执行next()方法,则迭代该子菜单了 if (component instanceof CompositeMenu || component instanceof CompositeMenu_ArrayVer) { stack.push(component.createIterator()); } return component; } }
Java
Waitress类的printVegetarinMenu()方法适当修改
public class Waitress { /** * 打印蔬菜食品 */ public HashMap<String,Double> getVegetarianMenu(){ var hashMap=new HashMap<String,Double>(); for(int i=0;i<allMenus.getLength();i++){ var menuComponent=allMenus.get(i); var iterator=menuComponent.createIterator(); while(iterator.hasNext()){ var component=iterator.next(); if(component.isVegetable()){ hashMap.put(component.getName(),component.getPrice()); } } } return hashMap; } public void printVegetarianMenu() { var hashMap=getVegetarianMenu(); for(var ele:hashMap.entrySet()){ System.out.println(ele.getKey()+" : "+ele.getValue()); } } }
Java
然后在main函数中进行测试
层次结构:
代码
public class Main2TestFinal { public static void main(String[] args) { // 建造烧饼菜单 MenuComponent pancakeMenu = new CompositeMenu("早餐", "原烧饼店菜单"); pancakeMenu.add(new MenuItem("葱花饼", 3.5, true)); pancakeMenu.add(new MenuItem("牛肉饼", 6, false)); pancakeMenu.add(new MenuItem("猪肉饼", 5, false)); pancakeMenu.add(new MenuItem("韭菜饼", 2.5, true)); //饮料菜单 MenuComponent drinkMenu=new CompositeMenu_ArrayVer("饮料","正餐子菜单"); drinkMenu.add(new MenuItem("冰可乐",3)); drinkMenu.add(new MenuItem("冰雪碧",3)); // 建造正餐菜单子菜单-甜点菜单 MenuComponent dessertMenu = new CompositeMenu_ArrayVer("甜点", "正餐子菜单"); dessertMenu.add(new MenuItem("芒果牛奶冰", 8, false)); dessertMenu.add(new MenuItem("珍珠果豆花", 6, false)); dessertMenu.add(new MenuItem("榴莲蛋黄", 8, false)); // 建造正餐菜单 MenuComponent dinnerMenu = new CompositeMenu_ArrayVer("正餐", "原饭店菜单"); dinnerMenu.add(new MenuItem("青椒肉丝炒饭", 12, false)); dinnerMenu.add(new MenuItem("番茄鸡蛋炒饭", 10, true)); dinnerMenu.add(new MenuItem("鱼香茄子炒饭", 11, false)); dinnerMenu.add(new MenuItem("土豆烧鸡炒饭", 12.5, false)); dinnerMenu.add(drinkMenu); dinnerMenu.add(dessertMenu); // 建造主菜单 MenuComponent totalMenu = new CompositeMenu("菜单", "总菜单"); totalMenu.add(pancakeMenu); totalMenu.add(dinnerMenu); // 服务员打印菜单 Waitress waitress = new Waitress(totalMenu); System.out.println("\ncomposite+iterator模式:\n"); waitress.printMenu(); System.out.println("\n蔬菜菜单:"); waitress.printVegetarianMenu(); } }
Java
结果
composite+iterator模式: 菜单名称:菜单(总菜单) 菜单名称:早餐(原烧饼店菜单) 葱花饼:3.5 牛肉饼:6.0 猪肉饼:5.0 韭菜饼:2.5 菜单名称:正餐(原饭店菜单) 青椒肉丝炒饭:12.0 番茄鸡蛋炒饭:10.0 鱼香茄子炒饭:11.0 土豆烧鸡炒饭:12.5 菜单名称:饮料(正餐子菜单) 冰可乐:3.0 冰雪碧:3.0 菜单名称:甜点(正餐子菜单) 芒果牛奶冰:8.0 珍珠果豆花:6.0 榴莲蛋黄:8.0 蔬菜菜单: 番茄鸡蛋炒饭 : 10.0 韭菜饼 : 2.5 葱花饼 : 3.5
Java
通过上面,就完成了composite模式与iterator模式的结合。
参考文章:
Modified based on gine-blog