为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。
一:理解
- 所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。——《Head First设计模式》
- 迭代器(iterator)是一种对象,它包含集合对象如List,对外提供统一的方式来遍历其所包含的集合。
二:例子
你是一个高富帅。
你有很多女朋友,你希望能找到一种优雅的方式对她们进行管理。
至于你为什么有那么多女朋友,因为你的套路实在太深。
于是,你叫来程序员小菜帮你写一个“女朋友们”管理程序。
小菜上来就是一顿敲,抽象出Beauty类。
@Data
public class Beauty {
private String name;
public Beauty(String name) {
this.name = name;
}
public void papapa() {
System.out.println("如果你追到" + name + "," + name + "就和你嘿嘿嘿!");
}
}
Beauty类包含名字name属性,还有papapa方法。
小菜想到可以为你建立一个女朋友花名册。
作为跟随你最多年,也最了解你的的程序员,小菜决定制作两本名册,分别是外国女朋友花名册和本地女朋友花名册。
由于小菜最近迷上了freeStyle,根本无心写这个程序。
于是他让小王和小李帮忙制作这两本花名册。
小王和小李的做事风格和小菜几乎一毛一样,不经过方案讨论,上来也是一顿敲。
首先看小王的代码,小王用List来保存富二代的所有外国女朋友。
public class ForeignBeautyRegister {
private List beautyList;
public ForeignBeautyRegister() {
beautyList = Lists.newArrayList();
beautyList.add(new Beauty("Mary"));
beautyList.add(new Beauty("Lily"));
beautyList.add(new Beauty("Tracy"));
beautyList.add(new Beauty("Angelia"));
}
public void addBeauty(Beauty beauty) {
beautyList.add(beauty);
}
public List getBeautyList() {
return beautyList;
}
}
再看小李的代码,小李用数组来保存富二代的所有当地女朋友。
数组的容量需要在初始化时规定。
小李认为你会在完成百人斩之后收手,所以设定为100。
public class LocalBeautyRegister {
private Beauty[] beauties;
private int numOfBeauty = 0;
public LocalBeautyRegister() {
beauties = new Beauty[100];
beauties[0] = new Beauty("小红");
beauties[1] = new Beauty("小黄");
beauties[2] = new Beauty("小紫");
beauties[3] = new Beauty("小绿");
}
public void addBeauty(Beauty beauty) {
if (numOfBeauty > 100) {
System.out.println("已经完成百人斩");
} else {
beauties[numOfBeauty] = beauty;
}
}
public Beauty[] getBeauties() {
return beauties;
}
}
小菜拿到这两份代码,还是一看也不看,直接拿给你看,并且写了一个测试代码,告诉你,这个程序应该这么玩。
public class Client {
public static void main(String[] args) {
ForeignBeautyRegister foreignBeautyRegister = new ForeignBeautyRegister();
LocalBeautyRegister localBeautyRegister = new LocalBeautyRegister();
List beautyList = foreignBeautyRegister.getBeautyList();
for (int i = 0; i < beautyList.size(); i++) {
beautyList.get(i).papapa();
}
System.out.println();
Beauty[] beauties = localBeautyRegister.getBeauties();
for (int i = 0; i < beauties.length; i++) {
Beauty beauty = beauties[i];
if (beauty == null) {
break;
}
beauties[i].papapa();
}
}
}
你觉得这个程序还OK,只是你发现与外国女朋友们papapa和与当地女朋友papapa时,需要用不同的遍历方法。
你当场没说,只是叹了一口气。
小菜知道你的脾气,为了防止被杀了祭天,就把锅甩给了小王和小李。
小菜知道,如果不对花名册程序进行重构,下次被拿来祭天的肯定是自己。
于是他对花名册程序进行重构。
他发现,你所需要的不过是对两个花名册进行遍历,只是花名册内部包含女朋友的数据结构遍历方式不一样。
于是,小菜决定使用迭代器模式。
为了约束两个类使用相同的遍历方法,小菜首先抽象了一个迭代器接口。
public interface Iterator {
boolean hasNext();
Object next();
}
然后,分别写下外国女朋友花名册迭代器类和当地女朋友花名册迭代器类。
public class ForeignBeautyIterator implements Iterator {
private List beautyList;
private int position = 0;
public ForeignBeautyIterator(List beautyList) {
this.beautyList = beautyList;
}
@Override
public boolean hasNext() {
if (position > beautyList.size() - 1 || beautyList.get(position) == null) {
return false;
}
return true;
}
@Override
public Object next() {
Beauty beauty = beautyList.get(position);
position++;
return beauty;
}
}
public class LocalBeautyIterator implements Iterator {
private Beauty[] beauties;
private int position = 0;
public LocalBeautyIterator(Beauty[] beauties) {
this.beauties = beauties;
}
@Override
public boolean hasNext() {
if (position > 99 || beauties[position] == null) {
return false;
}
return true;
}
@Override
public Object next() {
Beauty beauty = beauties[position];
position++;
return beauty;
}
}
可以看到的是,两个迭代器虽然持有不同的数据结构(List和数组),但对外提供的遍历方法名称都一致,分别是Iterator接口中规定的hasNext和next方法。
经过迭代器模式的重构,你在papapa时,无论是拿到外国女友花名册还是本地女朋友花名册,都只需要使用continuousPapapa方法即可。
continuousPapapa方法的入参是花名册的迭代器类,迭代器中包含的集合即为花名册中包含的集合,作为迭代器的构造器参数传入即可。
在continuousPapapa方法中,无需关注传入的是哪个迭代器类,遍历的方法都一样,基于hasNext和next方法。
public class ClientV2 {
public static void main(String[] args) {
ForeignBeautyRegister foreignBeautyRegister = new ForeignBeautyRegister();
LocalBeautyRegister localBeautyRegister = new LocalBeautyRegister();
ForeignBeautyIterator foreignBeautyIterator = new ForeignBeautyIterator(foreignBeautyRegister.getBeautyList());
LocalBeautyIterator localBeautyIterator = new LocalBeautyIterator(localBeautyRegister.getBeauties());
continuousPapapa(foreignBeautyIterator);
System.out.println();
continuousPapapa(localBeautyIterator);
}
public static void continuousPapapa(Iterator iterator) {
while (iterator.hasNext()) {
Beauty beauty = (Beauty) iterator.next();
beauty.papapa();
}
}
}
你很开心,总算找到一个好方法来管理你的女朋友们。
而且,可以用不同的数据结构来满足不同的保存需求。
只需新建新的迭代器类,你和女朋友们papapa的姿势就可以保持不变。
你准备请小菜吃火锅。
然而,结果是你吃火锅,小菜吃火锅底料。
三:再理解
- 例子中可以将两个花名册都修改成用List保存,但现实中存在不能修改的情况。
- 迭代器可以用于遍历不同的数据结构,其实是在数据结构之上多了一层方法包装。使用接口Iterator的约束这些方法,所以看上去,所有的数据结构都可以用相同的方法进行遍历。
- 迭代器模式对修改关闭,对增加开放。