迭代器模式:提供一种方法顺序访问一个聚合对象中的元素,而不暴露其内部的结构。
组合模式:允许将对象组成树形结构来表现“整体/部分”的层次结构。组合能让客户以一致的方法处理对象的组合和个别对象。
概要
第1部分 问题引入
餐厅和煎饼屋要合并,要把煎饼屋的菜单作为早餐菜单,餐厅的菜单作为午餐菜单。但是对于菜单项的记录,前者用的是ArrayList,后者用的是数组,两者都不愿意改变代码实现。所以在女招待处理的时候,需要用不同的方法分别处理这两个菜单,毕竟菜单项的返回值一个是ArrayList一个是数组,遍历的时候也要分别遍历。
之前的学习中一直说要封装变化的部分,但是由不同的集合类型所造成的遍历也可以封装吗?迭代器模式就是解决这个问题。
首先看下菜单项的代码:
菜单项:
package firsthead.interater;
/**
* @ClassName: MenuItem
* @Description: 菜单项
* @author xingle
* @date 2014年7月31日 下午9:51:11
*/
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 double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}
public String toString() {
return (name + ", $" + price + "\n " + description);
}
}
下面是煎饼屋和餐厅的菜单:
package firsthead.interater;
import java.util.ArrayList;
/**
* @ClassName: PancakeHouseMenu
* @Description: 煎饼屋菜单
* @author xingle
* @date 2014年7月31日 下午10:09:33
*/
public class PancakeHouseMenu {
ArrayList menuItems;
public PancakeHouseMenu() {
// 使用ArrayList存储菜单项
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",
"Waffles, with your choice of blueberries or strawberries",
true, 3.59);
}
// 加入一个菜单项:创建一个新的菜单项对象,加入ArrayList
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 Iterator createIterator(){
return new PancakeHouseMenuIterator(menuItems);
}
//其他方法。。。。
}
package firsthead.interater;
/**
* @ClassName: DinerMenu
* @Description: 餐厅菜单
* @author xingle
* @date 2014年7月31日 下午10:18:17
*/
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
// 使用数组形式
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);
addItem("Steamed Veggies and Brown Rice",
"Steamed vegetables over brown rice", true, 3.99);
addItem("Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true, 3.89);
}
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 = numberOfItems + 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
public Iterator createIterator(){
return new DinerMenuIterator(menuItems);
}
// 其他的方法
}
为了封装打印菜单的遍历,上面代码分别新建了一个菜单的迭代器createIterator(), 其中PancakeHouseMenuIterator(),和DinerMenuIterator()的迭代器代码如下:
package firsthead.interater;
import java.util.ArrayList;
/**
* @ClassName: PancakeHouseMenuIterator
* @Description: 实现一个具体的迭代器,煎饼屋菜单迭代器
* @author xingle
* @date 2014年8月1日 下午2:12:37
*/
public class PancakeHouseMenuIterator implements Iterator{
ArrayList items;
int position =0;
public PancakeHouseMenuIterator(ArrayList items){
this.items = items;
}
/*
* Title: hasNext
* Description:
* @return
* @see firsthead.interater.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
if (position >= items.size()) {
return false;
} else {
return true;
}
}
/*
* Title: next
* Description:
* @return
* @see firsthead.interater.Iterator#next()
*/
@Override
public Object next() {
Object object = items.get(position);
position = position + 1;
return object;
}
}
package firsthead.interater;
/**
* @ClassName: DinerMenuIterator
* @Description: 实现一个具体的迭代器,餐厅菜单迭代器
* @author xingle
* @date 2014年8月1日 下午1:56:06
*/
public class DinerMenuIterator implements Iterator{
MenuItem[] items;
//记录当前数组遍历的位置
int position = 0;
//构造器需要传入一个菜单项的数组当做参数
public DinerMenuIterator(MenuItem[] items){
this.items = items;
}
/*
* Title: hasNext
* Description:
* @return
* @see firsthead.interater.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
}
}
/*
* Title: next
* Description: 返回数组下一项并递增其位置
* @return
* @see firsthead.interater.Iterator#next()
*/
@Override
public Object next() {
MenuItem menuItem = items[position];
position = position + 1;
return menuItem;
}
}
以上两个迭代器都继承同一个迭代器Iterator,这里我们自己创建该迭代器:
package firsthead.interater;
/**
* @ClassName: Iterator
* @Description: 自定义迭代器
* @author xingle
* @date 2014年8月1日 下午1:54:56
*/
public interface Iterator {
boolean hasNext();
Object next();
}
下面是女招待的代码:
package firsthead.interater;
/**
* @ClassName: Waitress
* @Description: 女招待的代码
* @author xingle
* @date 2014年8月1日 下午2:19:46
*/
public class Waitress {
//煎饼屋菜单
PancakeHouseMenu pancakeHouseMenu;
//餐厅菜单
DinerMenu dinerMenu;
//在构造器中,女招待照顾两个菜单
public Waitress(PancakeHouseMenu pancakeHouseMenu,DinerMenu 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);
}
/**
* 打印菜单
* @Description:
* @param iterator
* @author xingle
* @date 2014年8月1日 下午3:29:11
*/
private void printMenu(Iterator iterator) {
while(iterator.hasNext()){
MenuItem menuItem = (MenuItem) iterator.next();
System.out.println("名称:"+menuItem.getName()+",");
System.out.println("价格:"+menuItem.getPrice()+",");
System.out.println("描述:"+menuItem.getDescription());
System.out.println("");
}
}
}
最后,我们写一个测试程序,看看女招待如何工作:
package firsthead.interater;
/**
* @ClassName: MenuTestDrive
* @Description: 测试程序
* @author xingle
* @date 2014年8月1日 下午3:35:05
*/
public class MenuTestDrive {
public static void main(String[] args){
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
执行以上程序,结果如下:
MENU:
----
BREAKFAST
名称:K&B's Pancake Breakfast,
价格:2.99,
描述:Pancakes with scrambled eggs, and toast
名称:Regular Pancake Breakfast,
价格:2.99,
描述:Pancakes with fried eggs, sausage
名称:Blueberry Pancakes,
价格:3.49,
描述:Pancakes made with fresh blueberries
名称:Waffles,
价格:3.59,
描述:Waffles, with your choice of blueberries or strawberries
LUNCH
名称:Vegetarian BLT,
价格:2.99,
描述:(Fakin') Bacon with lettuce & tomato on whole wheat
名称:BLT,
价格:2.99,
描述:Bacon with lettuce & tomato on whole wheat
名称:Soup of the day,
价格:3.29,
描述:Soup of the day, with a side of potato salad
名称:Hotdog,
价格:3.05,
描述:A hot dog, with saurkraut, relish, onions, topped with cheese
名称:Steamed Veggies and Brown Rice,
价格:3.99,
描述:Steamed vegetables over brown rice
名称:Pasta,
价格:3.89,
描述:Spaghetti with Marinara Sauce, and a slice of sourdough bread
到目前为止,我们做了哪些工作呢:
1.菜单的实现已经封装起来了。女招待不知道菜单式如何存储菜单项集合的。
2.只要实现迭代,我们只需要一个循环,就可以多态地处理任何项的集合。
3.女招待现在只使用一个借口(迭代器)
现在菜单的接口完全一样,但是,还没有一个共同的接口,也就是说女招待仍然捆绑于两个具体的菜单类,下面我们再来修改一下。
先来看看目前的设计:
你可能会奇怪,为什么我们不用java的Iterator接口呢——之所以这么做,是为了让你了解如何从头创建一个迭代器。现在目的已经达到了,所以就要改变做法,开始使用java的Iterator接口了。
我们只需要将煎饼屋迭代器和餐厅迭代器所扩展的接口,即由我们自己的迭代器接口,改为java.util的迭代器接口即可。实际上,甚至更简单。其实不止java.util有迭代器接口,ArrayList也有一个返回一个迭代器的iterator()方法。换句话说,我们并不需要为ArrayList实现自己的迭代器。然后,这里我们仍然需要为餐厅菜单实现一个迭代器,因为餐厅菜单使用的是数组,而数组不支持iterator()方法。
我们从煎饼屋菜单开始,只需要删除煎饼屋迭代器类,然后在煎饼屋的代码前加上import java.util.Iterator,再改变下面这一行代码就可以了:
这样PancakeHouseMenu就完成了。
接着,处理DinerMenu,以符合java.util.Iterator的需求。
然后,我们需要给菜单一个共同的接口,然后再改一下女招待。
现在,我们需要让煎饼屋菜单类和餐厅菜单类都实现Menu接口,然后更新女招待的代码如下:
煎饼屋菜单和餐厅菜单的类,都实现了Menu接口,女招待可以利用接口(而不是具体的类)引用每一个菜单对象。这样,通过“针对接口编程,而不是针对实现编程”,我们就可以减少女招待和具体类之间的依赖。
下面看下具体的代码:
首先是煎饼屋菜单:
package firsthead.interater;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @ClassName: PancakeHouseMenu
* @Description: 煎饼屋菜单
* @author xingle
* @date 2014年7月31日 下午10:09:33
*/
public class PancakeHouseMenu implements Menu{
ArrayList menuItems;
public PancakeHouseMenu() {
// 使用ArrayList存储菜单项
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",
"Waffles, with your choice of blueberries or strawberries",
true, 3.59);
}
// 加入一个菜单项:创建一个新的菜单项对象,加入ArrayList
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 Iterator createIterator(){
//return new PancakeHouseMenuIterator(menuItems);
//这里不创建自己的迭代器,而是调用菜单项ArrayList的iterator()方法
return menuItems.iterator();
}
}
餐厅菜单:
package firsthead.interater;
import java.util.Iterator;
/**
* @ClassName: DinerMenu
* @Description: 餐厅菜单
* @author xingle
* @date 2014年7月31日 下午10:18:17
*/
public class DinerMenu implements Menu{
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
// 使用数组形式
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);
addItem("Steamed Veggies and Brown Rice",
"Steamed vegetables over brown rice", true, 3.99);
addItem("Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true, 3.89);
}
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 = numberOfItems + 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
public Iterator createIterator(){
return new DinerMenuIterator(menuItems);
}
// 其他的方法
}
餐厅迭代器:
package firsthead.interater;
import java.util.Iterator;//将引用的迭代器换成这个
/**
* @ClassName: DinerMenuIterator
* @Description: 实现一个具体的迭代器,餐厅菜单迭代器
* @author xingle
* @date 2014年8月1日 下午1:56:06
*/
public class DinerMenuIterator implements Iterator{
MenuItem[] items;
//记录当前数组遍历的位置
int position = 0;
//构造器需要传入一个菜单项的数组当做参数
public DinerMenuIterator(MenuItem[] items){
this.items = items;
}
/*
* Title: hasNext
* Description:
* @return
* @see firsthead.interater.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
}
}
/*
* Title: next
* Description: 返回数组下一项并递增其位置
* @return
* @see firsthead.interater.Iterator#next()
*/
@Override
public Object next() {
MenuItem menuItem = items[position];
position = position + 1;
return menuItem;
}
/*上面都没有动*/
/*
* Title: remove
* Description: 需要自己实现remove()方法,因为使用的是固定长度的数组,在remove()调用是后面的元素需要向前移动
* @see java.util.Iterator#remove()
*/
@Override
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;
}
}
}
女招待:
package firsthead.interater;
import java.util.Iterator;
/**
* @ClassName: Waitress
* @Description: 女招待的代码
* @author xingle
* @date 2014年8月1日 下午2:19:46
*/
public class Waitress {
//煎饼屋菜单
Menu pancakeHouseMenu;
//餐厅菜单
Menu dinerMenu;
/*//在构造器中,女招待照顾两个菜单
public Waitress(PancakeHouseMenu pancakeHouseMenu,DinerMenu dinerMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}*/
public Waitress(Menu pancakeHouseMenu,Menu 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);
}
/**
* 打印菜单
* @Description:
* @param iterator
* @author xingle
* @date 2014年8月1日 下午3:29:11
*/
private void printMenu(Iterator iterator) {
while(iterator.hasNext()){
MenuItem menuItem = (MenuItem) iterator.next();
System.out.println("名称:"+menuItem.getName()+",");
System.out.println("价格:"+menuItem.getPrice()+",");
System.out.println("描述:"+menuItem.getDescription());
System.out.println("");
}
}
}
其中Menu:
package firsthead.interater;
import java.util.Iterator;
/**
* @ClassName: Menu
* @Description: TODO
* @author xingle
* @date 2014年8月1日 下午4:36:46
*/
public interface Menu {
public Iterator createIterator();
}
测试程序不变:
package firsthead.interater;
/**
* @ClassName: MenuTestDrive
* @Description: 测试程序
* @author xingle
* @date 2014年8月1日 下午3:35:05
*/
public class MenuTestDrive {
public static void main(String[] args){
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
执行结果同上。
第2部分 迭代器模式定义
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式让我们能游走于聚合内的每一个元素,而又不暴露其内部的表示。
把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
迭代器模式的类图:
单一责任
如果允许我们的聚合实现它们内部的集合以及相关的操作和遍历的方法,又会如何?
这样做不好,因为这样我们给了这个类两个变化的原因:如果这个集合改变,这个类必须跟着改变;如果遍历的方式改变,这个类也必须跟着改变。
设计原则:一个类应该只有一个引起变化的原因。
这个原则告诉我们,应该尽量让每个类保持单一责任。
问题2 引入 对象村的咖啡厅也要并入进来,供应晚餐菜单
咖啡厅菜单如下:
下面重新做咖啡厅的代码:
package firsthead.interater;
import java.util.Hashtable;
import java.util.Iterator;
/**
*
* @ClassName: CafeMenu 咖啡馆菜单
* @author Xingle
* @date 2014-8-4 下午12:55:13
*/
public class CafeMenu implements Menu{
// 菜单项用hashtable形式
Hashtable menuItems = new Hashtable();
public CafeMenu() {
addItem("Veggie Burger and Air Fries",
"Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
true, 3.99);
addItem("Soup of the day",
"A cup of the soup of the day, with a side salad", false, 3.69);
addItem("Burrito",
"A large burrito, with whole pinto beans, salsa, guacamole",
true, 4.29);
}
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 Hashtable getItems() {
return menuItems;
}
/**
*
* @Description: TODO
* @return
* @author xingle
* @data 2014-8-4 下午1:50:59
*/
@Override
public Iterator createIterator() {
return menuItems.values().iterator();
}
}
改写女招待,让她认识咖啡厅菜单:
package firsthead.interater;
import java.util.Iterator;
/**
* @ClassName: Waitress
* @Description: 女招待的代码
* @author xingle
* @date 2014年8月1日 下午2:19:46
*/
public class Waitress {
//煎饼屋菜单
Menu pancakeHouseMenu;
//餐厅菜单
Menu dinerMenu;
//增加咖啡厅菜单
Menu cafeMenu;
/*//在构造器中,女招待照顾两个菜单
public Waitress(PancakeHouseMenu pancakeHouseMenu,DinerMenu dinerMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}*/
/*public Waitress(Menu pancakeHouseMenu,Menu dinerMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}*/
public Waitress(Menu pancakeHouseMenu,Menu dinerMenu,Menu cafeMenu){
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}
public void printMenu(){
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();//
System.out.println("MENU:\n----\nBREAKFAST");
//打印煎饼屋菜单
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
//打印餐厅菜单
printMenu(dinerIterator);
//咖啡厅菜单,打印出来
System.out.println("\nDINNER");
printMenu(cafeIterator);
}
/**
* 打印菜单
* @Description:
* @param iterator
* @author xingle
* @date 2014年8月1日 下午3:29:11
*/
private void printMenu(Iterator iterator) {
while(iterator.hasNext()){
MenuItem menuItem = (MenuItem) iterator.next();
System.out.println("名称:"+menuItem.getName()+",");
System.out.println("价格:"+menuItem.getPrice()+",");
System.out.println("描述:"+menuItem.getDescription());
System.out.println("");
}
}
}
最后测试程序:
package firsthead.interater;
/**
* @ClassName: MenuTestDrive
* @Description: 测试程序
* @author xingle
* @date 2014年8月1日 下午3:35:05
*/
public class MenuTestDrive {
public static void main(String[] args){
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
CafeMenu cafeMenu = new CafeMenu();
//Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);
waitress.printMenu();
}
}
执行结果:
MENU:
----
BREAKFAST
名称:K&B's Pancake Breakfast,
价格:2.99,
描述:Pancakes with scrambled eggs, and toast
名称:Regular Pancake Breakfast,
价格:2.99,
描述:Pancakes with fried eggs, sausage
名称:Blueberry Pancakes,
价格:3.49,
描述:Pancakes made with fresh blueberries
名称:Waffles,
价格:3.59,
描述:Waffles, with your choice of blueberries or strawberries
LUNCH
名称:Vegetarian BLT,
价格:2.99,
描述:(Fakin') Bacon with lettuce & tomato on whole wheat
名称:BLT,
价格:2.99,
描述:Bacon with lettuce & tomato on whole wheat
名称:Soup of the day,
价格:3.29,
描述:Soup of the day, with a side of potato salad
名称:Hotdog,
价格:3.05,
描述:A hot dog, with saurkraut, relish, onions, topped with cheese
名称:Steamed Veggies and Brown Rice,
价格:3.99,
描述:Steamed vegetables over brown rice
名称:Pasta,
价格:3.89,
描述:Spaghetti with Marinara Sauce, and a slice of sourdough bread
DINNER
名称:Soup of the day,
价格:3.69,
描述:A cup of the soup of the day, with a side salad
名称:Burrito,
价格:4.29,
描述:A large burrito, with whole pinto beans, salsa, guacamole
名称:Veggie Burger and Air Fries,
价格:3.99,
描述:Veggie burger on a whole wheat bun, lettuce, tomato, and fries
问题3 引入
这时,餐厅需要创建一份甜点菜单,并将它插入到常规菜单中。即要支持菜单中的菜单。