使用组合模式(Composite Pattern)是一个更优雅的方式来表示菜单和菜单项。组合模式允许我们将单个对象(如菜单项)和组合对象(如菜单)以相同的方式处理。
解决方案:
- 创建组合结构:我们将菜单项和菜单抽象为
MenuComponent
,其中菜单项是叶节点,菜单是组合节点。 Menu
类:可以包含子菜单或菜单项。MenuItem
类:代表具体的菜单项。Waitress
类:只需处理一个MenuComponent
对象,并能够遍历所有的菜单和菜单项。
代码实现
1. 创建抽象类 MenuComponent
import java.util.Iterator;
public abstract class MenuComponent {
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 String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
public Iterator<MenuComponent> createIterator() {
throw new UnsupportedOperationException();
}
}
2. 实现 MenuItem
类(叶节点)
public class MenuItem extends MenuComponent {
private String name;
private String description;
private double price;
public MenuItem(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.println(" " + getName() + ": " + getDescription() + " -- ¥" + getPrice());
}
}
3. 实现 Menu
类(组合节点)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Menu extends MenuComponent {
private List<MenuComponent> menuComponents = new ArrayList<>();
private String name;
private String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void print() {
System.out.println("\n" + getName() + ": " + getDescription());
System.out.println("---------------------");
for (MenuComponent menuComponent : menuComponents) {
menuComponent.print();
}
}
}
4. 实现 Waitress
类
public class Waitress {
private MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}
5. 修改测试代码
public class CompositePatternDemo {
public static void main(String[] args) {
MenuComponent breakfast = new Menu("早餐菜单", "早餐6:00~9:00");
MenuComponent lunch = new Menu("午餐菜单", "午餐11:30~14:30");
MenuComponent coffeeMenu = new Menu("咖啡菜单", "全天24小时供应");
MenuComponent allMenus = new Menu("所有菜单", "所有可使用的菜单");
// Add breakfast items
breakfast.add(new MenuItem("胡辣汤", "素胡辣汤", 3));
breakfast.add(new MenuItem("油条", "按斤称", 2));
breakfast.add(new MenuItem("包子", " 莲藕馅", 1.5));
// Add lunch items
lunch.add(new MenuItem("茄汁面", "番茄鸡蛋汤面", 8));
lunch.add(new MenuItem("蒜汁面", "凉拌面", 7));
lunch.add(new MenuItem("臊子面", "羊肉臊子面", 15));
// Add coffee items
coffeeMenu.add(new MenuItem("生椰拿铁", "少冰", 9.99));
coffeeMenu.add(new MenuItem("丝绒拿铁", "热饮", 12.99));
coffeeMenu.add(new MenuItem("茉莉生椰拿铁", "外卖,少冰,不另外加糖", 16.99));
// Combine menus
allMenus.add(breakfast);
allMenus.add(lunch);
allMenus.add(coffeeMenu);
// Create waitress and print all menus
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
输出结果
所有菜单: 所有可使用的菜单
---------------------
早餐菜单: 早餐6:00~9:00
---------------------
胡辣汤: 素胡辣汤 -- ¥3.0
油条: 按斤称 -- ¥2.0
包子: 莲藕馅 -- ¥1.5
午餐菜单: 午餐11:30~14:30
---------------------
茄汁面: 番茄鸡蛋汤面 -- ¥8.0
蒜汁面: 凉拌面 -- ¥7.0
臊子面: 羊肉臊子面 -- ¥15.0
咖啡菜单: 全天24小时供应
---------------------
生椰拿铁: 少冰 -- ¥9.99
丝绒拿铁: 热饮 -- ¥12.99
茉莉生椰拿铁: 外卖,少冰,不另外加糖 -- ¥16.99
代码解释
MenuComponent
抽象类:定义了菜单和菜单项的通用操作,如add()
、remove()
、getChild()
等。组合对象(Menu
)可以包含MenuComponent
,而叶节点(MenuItem
)则只处理具体的菜单项。Menu
类:可以包含子菜单或菜单项,通过组合来管理多个菜单和子菜单。MenuItem
类:叶节点,表示具体的菜单项。Waitress
类:只需要处理一个MenuComponent
对象,无论是叶节点(菜单项)还是组合对象(菜单),都可以通过相同的方式处理并打印。
总结
通过使用组合模式,Menu
和 MenuItem
都被视为 MenuComponent
,Waitress
只需要一个 MenuComponent
对象即可遍历所有菜单和菜单项。这使得代码非常灵活,易于扩展和维护。
组合模式 (Composite Pattern) 详细解释
1. 组合模式的概念
组合模式允许我们将对象组合成树形结构来表示“部分-整体”的层次结构。它使得客户端能够以一致的方式处理单个对象和组合对象。
在我们的场景中,菜单项(MenuItem
)是基本的元素,菜单(Menu
)则是组合对象,可以包含其他菜单或菜单项。Waitress
类不需要知道它是在处理菜单还是菜单项,只要调用通用接口即可,这正是组合模式的强大之处。
2. 类结构概述
-
MenuComponent
:抽象基类,提供了所有菜单和菜单项的通用操作。它定义了组合和叶节点的接口,如add()
、remove()
、getChild()
、print()
等操作。由于菜单项不需要支持
add()
或getChild()
,而菜单可以包含其他菜单或菜单项,因此这些操作在基类中默认抛出UnsupportedOperationException
,具体的子类可以根据需要覆盖这些方法。 -
Menu
:组合对象类,代表一个菜单,可以包含多个MenuComponent
(既可以是子菜单,也可以是菜单项)。它的主要功能是管理子菜单和菜单项,提供添加、删除、获取子组件的操作。 -
MenuItem
:叶节点类,代表一个具体的菜单项。它没有子节点,所以不能包含其他MenuComponent
,只能提供自己的基本信息,如名称、描述和价格。 -
Waitress
:客户端类,接收一个MenuComponent
,可以遍历和打印所有菜单和菜单项。它对组合和叶节点一视同仁,只调用print()
方法即可打印出菜单结构。
3. 组合模式的工作原理
通过 MenuComponent
抽象类,Waitress
可以直接使用 MenuComponent
的统一接口,来处理菜单和菜单项的组合结构。组合模式的核心在于它让我们能够像处理单个对象一样,处理整个对象的组合。无论是遍历单个菜单项还是遍历包含多个子菜单的菜单,Waitress
类只需要关心调用通用的 print()
方法。
4. 组合模式的优点
-
一致性:客户端可以一致地处理叶节点(菜单项)和组合节点(菜单),使得客户端代码变得简洁且灵活。
在我们实现的
Waitress
类中,它只关心如何遍历和打印MenuComponent
,不需要区分是在处理一个具体的菜单项还是一个包含子菜单的组合对象。 -
可扩展性:添加新的菜单或菜单项变得非常简单。我们可以在组合中嵌套更多的子菜单,也可以轻松添加新的菜单项,甚至在
Menu
中组合多个Menu
。 -
简化客户端代码:由于客户端只需要处理抽象基类
MenuComponent
,不需要分别处理MenuItem
和Menu
,客户端代码变得非常简洁。这种设计也让系统变得更灵活和可维护。
5. 组合模式中的递归
组合模式的核心在于它是一个递归结构:菜单可以包含子菜单,子菜单又可以包含更多的菜单或菜单项。这种递归结构允许我们通过遍历树形结构来处理所有的菜单项。Menu
的 print()
方法就是递归调用,遍历所有子菜单和菜单项。
6. 与迭代器模式的对比
虽然组合模式和迭代器模式都能处理多个对象,但它们的目标和使用场景有所不同:
-
迭代器模式:用于顺序遍历一个集合中的元素,通常用在结构较为扁平的情况下,例如遍历数组或列表中的元素。之前的实现中,我们通过多个
Iterator
来遍历不同菜单,这更适合扁平化的数据结构。 -
组合模式:适合处理树形结构(如菜单-子菜单-菜单项),在这种情况下,组合模式能够让客户端以统一的方式处理复杂的层次结构。