在软件设计中,我们常常会遇到各种复杂的对象集合。如何优雅地遍历这些集合?又如何处理像树一样具有层次结构的对象?迭代器模式和组合模式正是为解决这些问题而生的两大利器。很多人会混淆它们,但它们其实是职责分明、又能完美协作的好伙伴。
今天,我们就来一起揭开它们的神秘面纱。
第一部分:迭代器模式——集合的“万能遥控器”
想象一下,你有一个万能遥控器,可以遍历你家所有的电器:电视、空调、音响。你不需要知道每个电器内部是如何换台、调温或切歌的,你只需要按“下一个”按钮。迭代器模式就是这个“万能遥控器”。
1.1 核心思想
迭代器模式提供一种方法,使得我们可以顺序访问一个聚合对象(集合)中的各个元素,而又不需要暴露该对象的内部表示。
它的目标很明确:将遍历行为从集合对象中分离出来。这样,无论是数组、链表、二叉树还是哈希表,客户端都可以用同一套方式(next(), hasNext())来遍历,大大降低了耦合度。
1.2 角色构成
-
迭代器接口:定义访问和遍历元素的方法,如
hasNext(),next()。 -
具体迭代器:实现迭代器接口,负责管理当前遍历的位置。
-
聚合接口:定义创建迭代器对象的方法,如
createIterator()。 -
具体聚合:实现聚合接口,返回一个与自身对应的具体迭代器实例。
1.3 简单代码示例
// 迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
// 聚合接口
public interface Menu {
Iterator createIterator();
}
// 具体聚合 - 早餐菜单(使用数组)
public class BreakfastMenu implements Menu {
private MenuItem[] items;
@Override
public Iterator createIterator() {
return new BreakfastMenuIterator(items);
}
}
// 具体迭代器
public class BreakfastMenuIterator implements Iterator {
private MenuItem[] items;
private int position = 0;
public BreakfastMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.length && items[position] != null;
}
@Override
public Object next() {
MenuItem item = items[position];
position++;
return item;
}
}
使用迭代器的客户端代码变得异常简洁:
public class Waitress {
private Menu breakfastMenu;
public void printMenu() {
Iterator iterator = breakfastMenu.createIterator();
while (iterator.hasNext()) {
MenuItem item = (MenuItem) iterator.next();
System.out.println(item.getName() + " - $" + item.getPrice());
}
}
}
优势:Waitress类完全不知道菜单项是存储在数组还是列表中,它只依赖 Iterator接口。如果 BreakfastMenu的内部数据结构改变了,Waitress的代码无需任何修改。
第二部分:组合模式——统一的“树形结构”处理专家
现在,考虑一个更复杂的场景:你的菜单本身可以包含子菜单(比如“晚餐菜单”下包含“甜品子菜单”)。这就是一个树形结构。组合模式就是用来处理这种“部分-整体”层次结构的专家。
2.1 核心思想
组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。它使得用户对单个对象(叶子节点)和组合对象(树枝节点)的使用具有一致性。
简单来说,它让我们可以用处理文件的方式去处理文件夹——对两者都执行“打开”操作。对文件是打开本身,对文件夹是打开其窗口。
2.2 角色构成
-
组件:是组合中所有对象(叶子和组合)的通用接口,声明了如
print()等操作。 -
叶子:表示叶子节点对象,没有子节点。实现组件接口的行为。
-
组合:表示树枝节点对象,用于存储子部件(这些子部件可以是叶子,也可以是另一个组合),并实现管理子部件的方法(如
add,remove)。
2.3 代码示例
// 组件接口
public abstract class MenuComponent {
// 组合相关操作(默认抛出异常,叶子节点不支持)
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
// 遍历相关操作(由组合节点实现)
public abstract Iterator createIterator();
// 公共操作
public String getName() { ... }
public void print() { ... }
}
// 叶子节点 - 菜单项
public class MenuItem extends MenuComponent {
private String name;
private double price;
@Override
public String getName() { return name; }
@Override
public void print() {
System.out.println(" " + getName() + " - $" + getPrice());
}
@Override
public Iterator createIterator() {
// 菜单项是叶子,没有子元素,返回一个空迭代器
return new NullIterator();
}
}
// 组合节点 - 菜单(可包含子菜单或菜单项)
public class Menu extends MenuComponent {
private List<MenuComponent> menuComponents = new ArrayList<>();
private String name;
public Menu(String name) { this.name = name; }
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public void print() {
System.out.println("\n" + getName());
System.out.println("----------------");
// 递归打印所有子组件
for (MenuComponent component : menuComponents) {
component.print(); // 这里统一调用了组件的print方法,无论是Menu还是MenuItem
}
}
@Override
public Iterator createIterator() {
// 关键!返回一个能遍历所有子组件的迭代器
return new CompositeIterator(menuComponents.iterator());
}
}
优势:客户端可以忽略组合对象和单个对象的区别,统一地使用树形结构中的所有对象。
第三部分:强强联合——当组合模式需要遍历时
现在,最精彩的部分来了!如果我们想遍历整个菜单树(包括所有菜单和子菜单),该怎么做?直接在客户端写递归代码吗?这会让客户端变得复杂且与组合结构紧耦合。
答案是:让组合模式与迭代器模式结合!
我们为组合结构中的 Menu(组合类)创建一个特殊的迭代器——CompositeIterator。
3.1 组合迭代器
这个迭代器的任务是遍历一个组合对象的所有子组件。由于子组件本身可能也是组合对象(子菜单),所以需要用到栈(Stack)或队列(Queue)来实现深度优先或广度优先遍历。
// 一个深度优先的组合迭代器
public class CompositeIterator implements Iterator {
private Stack<Iterator<MenuComponent>> stack = new Stack<>();
public CompositeIterator(Iterator<MenuComponent> iterator) {
stack.push(iterator);
}
@Override
public boolean hasNext() {
if (stack.empty()) {
return false;
}
Iterator<MenuComponent> iterator = stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext(); // 递归检查栈中的下一个迭代器
} else {
return true;
}
}
@Override
public MenuComponent next() {
if (!hasNext()) {
return null;
}
Iterator<MenuComponent> iterator = stack.peek();
MenuComponent component = iterator.next();
if (component instanceof Menu) { // 如果当前组件是子菜单,则将其迭代器压入栈中
stack.push(component.createIterator());
}
return component;
}
}
3.2 最终的威力
现在,我们的女招待代码可以强大到遍历整个复杂的菜单树,而代码却依然简洁:
public class PowerfulWaitress {
private MenuComponent allMenus; // 指向菜单树的根节点
public PowerfulWaitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printVegetarianMenu() {
// 1. 从根菜单创建组合迭代器
Iterator iterator = allMenus.createIterator();
System.out.println("\n--- VEGETARIAN MENU ---");
// 2. 遍历整个树形结构
while (iterator.hasNext()) {
MenuComponent component = (MenuComponent) iterator.next();
try {
// 3. 对每个元素,我们统一对待。如果是素食菜单项,则打印。
if (component.isVegetarian()) {
component.print();
}
} catch (UnsupportedOperationException e) {
// Menu(组合)节点不支持isVegetarian操作,会抛出异常,这里忽略即可。
}
}
}
}
总结与对比
|
特性 |
迭代器模式 |
组合模式 |
|---|---|---|
|
意图 |
解耦遍历逻辑,提供统一的遍历接口。 |
统一处理层次结构,让客户一致对待个体和组合。 |
|
关注点 |
行为(如何访问元素) |
结构(如何组织元素) |
|
关键比喻 |
万能遥控器 |
文件系统(文件/文件夹) |
|
关系 |
最佳搭档。组合模式构建的复杂结构,正需要迭代器模式来提供优雅的遍历能力。 |
总而言之,迭代器模式和组合模式并非同一种模式,它们各有专攻。迭代器模式专注于“如何遍历”,而组合模式专注于“如何构建树形结构”。但当它们结合在一起时,就能以极其优雅的方式处理和管理复杂的对象树,这正是设计模式的魅力所在——它们像积木一样,可以组合出更强大、更灵活的解决方案。
2万+

被折叠的 条评论
为什么被折叠?



