一、模式简介
迭代器相信大家都不陌生,在我们学习java的集合框架时,遍历集合的元素可以使用不同的for
也可以使用迭代器来遍历集合中每个元素,那么这两种方式有什么不同的地方呢?我们先来引入迭代器的概念,再进行探讨。迭代器:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
二、模式详解
通过迭代器的概念我们可以知道,迭代器可以不用暴露聚合对象的结构来获取集合对象的元素。比如有两个元素集合,一种采用数组的结构(arr),另一种采用集合的形式(list),在使用for
循环进行遍历代码时就会写出如下的代码 int a=arr[i]
和int b=list.get(i)
,那么这样的循环就会暴露其内部的表示。而这又有什么坏处呢?我以一个例子来为大家解答一下。
现在有一家餐厅,这家餐厅有两张菜单,一张是早餐的菜单,而另一张是晚餐的菜单,这两种菜单的实现方式分别由数组和集合实现,当服务员想给客户展示它们家的菜都有什么时,就会有如下的代码:
- 菜单项
/**
* 菜单项
*
*/
@Data
@AllArgsConstructor
public class MenuItem {
/**
* 名字
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 价格
*/
double price;
}
- 早餐
/**
* 早餐菜单
*
* @author 张鹏
* @date 2022/03/13
*/
public class BreakfastMenu {
ArrayList menuItems;
public BreakfastMenu(){
menuItems=new ArrayList<>();
addMenuItems("豆浆","新鲜鲜榨豆浆",1.5);
addMenuItems("油条","高能量油条",1.5);
addMenuItems("包子","皮包馅大",2);
}
public void addMenuItems(String name,String description,double price) {
MenuItem menuItem=new MenuItem(name,description,price);
menuItems.add(menuItem);
}
public ArrayList getMenuItems(){
return menuItems;
}
}
- 晚餐
/**
* 晚餐菜单
*
*/
public class DinnerMenu {
static final int MAX_TIMES=6;
int numberOfItems=0;
MenuItem[] menuItems;
public DinnerMenu(){
menuItems=new MenuItem[MAX_TIMES];
addMenuItems("麻婆豆腐","麻辣可口",20);
addMenuItems("可乐鸡翅","鲜嫩可口",20);
addMenuItems("米饭","米香浓郁",2);
}
public void addMenuItems(String name,String description,double price) {
if (numberOfItems>MAX_TIMES){
System.out.println("抱歉菜单已经满了");
}else {
MenuItem menuItem=new MenuItem(name,description,price);
menuItems[numberOfItems]=menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems(){
return menuItems;
}
}
- 服务员
/**
* 女服务员
*
*/
public class Waitress {
BreakfastMenu breakfastMenu;
DinnerMenu dinnerMenu;
public Waitress(BreakfastMenu breakfastMenu,DinnerMenu dinnerMenu){
this.breakfastMenu=breakfastMenu;
this.dinnerMenu=dinnerMenu;
}
public void printMenu(){
ArrayList breakfastMenuItems = breakfastMenu.getMenuItems();
for (int i = 0; i < breakfastMenuItems.size(); i++) {
MenuItem o = (MenuItem) breakfastMenuItems.get(i);
System.out.println("名称"+o.getName());
System.out.println("描述"+o.getDescription());
System.out.println("价格"+o.getPrice());
}
MenuItem[] dinnerMenuItems = dinnerMenu.getMenuItems();
for (int i = 0; i <dinnerMenuItems.length ; i++) {
MenuItem o=dinnerMenuItems[i];
System.out.println("名称"+o.getName());
System.out.println("描述"+o.getDescription());
System.out.println("价格"+o.getPrice());
}
}
}
这样写的坏处很明显,比如:当有新的菜单时服务员还要再写一个循环并且说服务员要知道每个菜单要如何表达内部的菜单集合,这违反了封装。下面我们来用迭代器改造下,这里我们自定义Iterator
接口而不使用util包中提供的。
- Iterator接口
/**
* 这里我们自定义一个迭代器(也可以使用Util包中的)
*
*/
public interface Iterator{
boolean hasNext();
Object next();
}
- 自定义的晚餐迭代器
public class DinnerIterator implements Iterator{
MenuItem[] menuItems;
int position=0;
public DinnerIterator(MenuItem[] menuItems){
this.menuItems=menuItems;
}
@Override
public boolean hasNext() {
if (position>=menuItems.length||menuItems[position]==null){
return false;
}else {
return true;
}
}
@Override
public Object next() {
MenuItem menuItem=menuItems[position];
position++;
return menuItem;
}
}
- 午餐迭代器
public class BreakfastIterator implements Iterator {
List menuItems;
int position=0;
public BreakfastIterator(ArrayList menuItems){
this.menuItems=menuItems;
}
@Override
public boolean hasNext() {
if (position>=menuItems.size()||menuItems.get(position)==null){
return false;
}else {
return true;
}
}
@Override
public Object next() {
MenuItem menuItem= (MenuItem) menuItems.get(position);
position++;
return menuItem;
}
}
-
生成迭代器接口
目的为了让服务员依赖于抽象的迭代器,而不依赖于现实
/**
* 使用工厂方法,让每个聚合为我们提供一个迭代器
*
*/
public interface createIterator {
/**
* 创建迭代器
*
*
*/
Iterator createIterator();
}
- 改变后的晚餐菜单
/**
* 晚餐菜单
*
*/
public class DinnerMenu implements createIterator{
static final int MAX_TIMES=6;
int numberOfItems=0;
MenuItem[] menuItems;
public DinnerMenu(){
menuItems=new MenuItem[MAX_TIMES];
addMenuItems("麻婆豆腐","麻辣可口",20);
addMenuItems("可乐鸡翅","鲜嫩可口",20);
addMenuItems("米饭","米香浓郁",2);
}
public void addMenuItems(String name,String description,double price) {
if (numberOfItems>MAX_TIMES){
System.out.println("抱歉菜单已经满了");
}else {
MenuItem menuItem=new MenuItem(name,description,price);
menuItems[numberOfItems]=menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems(){
return menuItems;
}
@Override
public Iterator createIterator() {
return new DinnerIterator(menuItems);
}
}
- 改变后的早餐菜单
/**
* 早餐菜单
*
* @author 张鹏
* @date 2022/03/13
*/
public class BreakfastMenu implements createIterator{
ArrayList menuItems;
public BreakfastMenu(){
menuItems=new ArrayList<>();
addMenuItems("豆浆","新鲜鲜榨豆浆",1.5);
addMenuItems("油条","高能量油条",1.5);
addMenuItems("包子","皮包馅大",2);
}
public void addMenuItems(String name,String description,double price) {
MenuItem menuItem=new MenuItem(name,description,price);
menuItems.add(menuItem);
}
/**
* 这里获取菜单的方法可以不用了
*
*
*/
public ArrayList getMenuItems(){
return menuItems;
}
@Override
public Iterator createIterator() {
return new BreakfastIterator(menuItems);
}
}
- 改变后的服务员代码
**
* 女服务员
*
*/
public class Waitress {
BreakfastMenu breakfastMenu;
DinnerMenu dinnerMenu;
public Waitress(BreakfastMenu breakfastMenu,DinnerMenu dinnerMenu){
this.breakfastMenu=breakfastMenu;
this.dinnerMenu=dinnerMenu;
}
public void printMenu(){
Iterator breakfastIterator = breakfastMenu.createIterator();
printMenu(breakfastIterator);
Iterator dinnerIterator1 = dinnerMenu.createIterator();
printMenu(dinnerIterator1);
}
public void printMenu(Iterator iterator){
while (iterator.hasNext()){
MenuItem o = (MenuItem) iterator.next();
System.out.println("名称"+o.getName());
System.out.println("描述"+o.getDescription());
System.out.println("价格"+o.getPrice());
}
}
}
这样通过我们自己实现一套迭代器,我们可以看到哪些好处呢?
- 服务员不需要知道,聚合内部如何表达,即可遍历集合中的元素。
- 通过迭代器,服务员只需要写一次循环,即可遍历各种菜单中的元素。
- 把游走的任务放在迭代器上,而不是聚合接口上。这样简化了聚合的接口和实现,也让责任各得其所。
我们了解到了迭代器的好处之后,再来观察下迭代器的类图(ps:写了这么久博客才会用UML):
从类图中我们可以发现服务员只关系menu和Iterator这两个抽象接口,同时我们可以发现迭代器的类图有些类似于工厂方法模式的类图,正是因为我们可以使用工厂方法来创建出不同的迭代器。
三、适用场景
-
当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
-
使用该模式可以减少程序中重复的遍历代码
-
如果你希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。