1.声明
设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》,作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。
在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。
2.优化
首先回顾一下上篇"迭代器模式"中女招待员的代码:
//女招待员
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
Menu cafeMenu; //咖啡菜单
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}
public void printMenu() {
Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
Iterator<MenuItem> dinerIterator = dinerMenu.createIterator();
Iterator<MenuItem> cafeIterator = cafeMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinerIterator);
System.out.println("\nDINNER");
printMenu(cafeIterator);
}
//打印菜单
private void printMenu(Iterator<MenuItem> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
通过观察我们发现,printMenu中调用了三次createIterator方法,并且还调用了三次打印的方法,那么如果我们又增加一些餐厅,就得再修改这两处代码,这违反了开放-关闭原则。
于是我们有了改进后的女招待员代码:
改进后的女招待员代码:
//女招待员
public class Waitress {
ArrayList<Menu> menus;
public Waitress(ArrayList<Menu> menus) {
this.menus = menus;
}
public void printMenu() {
Iterator<?> menuIterator = menus.iterator();
while(menuIterator.hasNext()) {
Menu menu = (Menu)menuIterator.next();
printMenu(menu.createIterator());
}
}
void printMenu(Iterator<?> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
正当我们觉得这很“安全“的时候,餐厅有提出了新的需求,他们希望创建一份甜点菜单,在午餐后(餐厅)提供。也就是说还需要菜单中的菜单。
新的需求:
显然对于这个新的需求,现有的代码肯定是行不通的,那么我们只能不幸的告诉厨师们,重构代码已经必不可免。、
项目达到一定级别,就必须重构代码,使他能够成长。如果不这么做,就会导致僵化并且没有弹性的代码,完全看不到萌发新生命的希望。
对于这份需求,需要我们做什么?
- 我们需要某种树状结构,可以容纳菜单,子菜单和菜单项。
- 我们需要确定能够在每个菜单的各个项之间游走,而且至少要像现在用迭代器一样方便。
- 我们也需要能够更有弹性地在菜单项之间游走。比方说,可能只需要遍历甜点菜单,或者可以遍历整个菜单(包括甜点菜单)。
3.组合模式
3.1定义组合模式
组合模式
允许你将对象组合成树状结构来表现"整体/部分"层次结构。组合能让客户以一致地方式处理个别对象以及对象组合。
菜单视角运用组合模式:
我们已菜单为例思考这一切:这个模式能够创建一个树状结构,在同一个结构中处理潜逃菜单和菜单项组。通过将菜单和项放在相同地的结构中,我们创建了一个"整体/部分"层次结构,即由菜单和菜单项组成的对象树。但是可以将它视为一个整体,像是一个丰富的大菜单。
一旦有了丰富的大菜单,我们就可以使用这个模式来"统一处理个别对象和组合对象"。这意味着,如果我们有了一个树形结构的菜单、子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种"组合”。因为它既可以包含其他菜单,也可以包含菜单项。个别对象只是菜单项——并未持有其他对象。使用一个遵循组合模式的设计,让我们可以写出简单的代码,就能够对整个菜单结构应用相同的操作(例如打印)。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别对象。
使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差异。
3.2组合模式类图
问答:
1.组合的思想在树状结构中是如何体现的?
答:组合包含组件。组件中又有两种表现形式:组合和叶子节点。这用到了递归思想。组合持有一群孩子,这些孩子可以是别的组合或者叶节点元素。当我们用这种方式组织数据的时候,最终会得到树状结构(准确的说是自上而下的树状结构),根部是一个组合,而组合的分支逐渐向下延伸,直到叶子节点为止。
3.3利用组合模式设计菜单
一开始我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项。换句话说,我们可以针对菜单和菜单项调用相同的方法。
以对象村餐厅的角度设计类图:
3.4代码设计
3.4.1实现菜单组件
菜单组件的角色时为叶节点和组合节点提供一个共同的接口,并且菜单组件中所有的方法都有默认实现,这样,如果菜单项(叶子节点)或者菜单(组合)不想实现某个方法的时候(例如叶子节点不想实现getChild()方法),就可以不是先这些方法。
所有的组件都必须实现MenuComponent接口,然而,叶子节点和组合节点的角色不同,所以有些方法可能并不适合某种节点,面对这种情况,我们需要将不需要的方法抛出异常。因为有些方法只对菜单项有意义,有的则支队菜单有意义,默认实现时抛出UnsupportedOperationException异常。这样,如果菜单项或菜单不支持某个操作,他们就不做任何事,直接继承默认实现就可以了。
菜单组件:
//菜单组件,方法默认抛出异常
public abstract class MenuComponent {
public void a