Hello,大家好:
今天我们来聊一下设计模式中迭代器模式。
背景:
甲、乙、丙三人合作开发一套人员管理系统,甲在编程中习惯使用数组存储元素,乙喜欢编程中习惯使用List等集合存储元素,丙在项目开发中刚好需要遍历甲、乙存储的元素。无奈,丙只能写两套不同的遍历方法,上述场景用代码描述为:
StudentContainer studentContainer = new StudentContainer();
List<Student> students = studentContainer.getStudents();
for (int i = 0; i < students.size(); i++) {
System.out.println(students.get(i).getStudentNum());
}
TeacherContainer teacherContainer = new TeacherContainer();
Teacher[] teachers = teacherContainer.getTeachers();
for (int i = 0; i < teachers.length; i++) {
System.out.println(teachers[i]);
}
注意,可能小伙伴们会说可以使用增强for循环呀,其实那也是JDK对迭代器模式的一种实现,在后续的博客中我们将从JDK字节码层面来看看增强for循环的原理。上述方式除了遍历方式不一致外,丙还得知道甲乙两人的内部存储细节。然后他们讨论,为了统一遍历方式,以及不暴露自身内部存储细节,对数据存储进行了改进。
好了,经过讨论,通用存储模式类图如下:
方法描述:addElement和removeElement方法用以增加和删除元素,next()、previous()、isFirst()等方法用以遍历元素。
那么上述方法存在什么问题呢?
1、该类既要负责数据存储,又要负责遍历数据,违反设计模式单一职责原则。
2、如果将该类声明为一个接口,交给子类去实现,那么这个接口必定包含大量方法,不利于子类去实现,违反接口隔离原则。
3、如果将遍历交给子类负责,子类就肯定要访问AbstractStoreElement中存储的元素,就必须暴露AbstractStoreElement的内部存储细节,不然子类无法进行遍历,又破坏了AbstractStoreElement的封装性。
于是甲乙丙三人将遍历元素的方法提取出来,将数据存储和数据遍历分离开来。无须暴露AbstractStoreElement的内部存储细节即可完成对它的遍历。这就是迭代器模式的意图所在。
最后经过改进得到的类图如下:
AbstractStoreElement(抽象存储类):用于存储和管理元素对象的抽象类,具体如何管理交由子类去实现,声明createIterator方法来创建一个迭代器。
ConcreteStorefElement(具体存储类):继承AbstractStoreElement,实现createIterator()方法,返回一个与该具体存储类对应的具体迭代器。
Iterator(抽象迭代器):定义访问和遍历元素的接口,声明用于遍历的方法。
ConcreteIterator(具体迭代器):它实现了抽象迭代器中的所有方法,完成对存储类中的元素的遍历操作。具体迭代器中通过游标来记录在聚合对象中所处的当前位置。
附录完整代码:
AbstractStoreElement:
public abstract class AbstractStoreElement {
// 声明创建迭代器对象的抽象工厂方法
public abstract AbstractIterator createIterator();
}
ConcreteStoreElement:
public class ConcreteStoreElement extends AbstractStoreElement {
protected List<Student> students = new ArrayList<Student>();
public ConcreteStoreElement(List<Student> students) {
this.students = students;
}
public void addObject(Student obj) {
this.students.add(obj);
}
public void removeObject(Object obj) {
this.students.remove(obj);
}
public List<Student> getObjects() {
return this.students;
}
// 实现创建迭代器对象的具体工厂方法
public AbstractIterator createIterator() {
return new ConcreteIterator(this);
}
}
AbstractIterator:
public interface AbstractIterator {
public void next(); // 移至下一个元素
public boolean isLast(); // 判断是否为最后一个元素
public void previous(); // 移至上一个元素
public boolean isFirst(); // 判断是否为第一个元素
public Object getNextItem(); // 获取下一个元素
public Object getPreviousItem(); // 获取上一个元素
}
ConcreteIterator:
public class ConcreteIterator implements AbstractIterator{
private List<Student> students;
private int cursor1; // 定义一个游标,用于记录正向遍历的位置
private int cursor2; // 定义一个游标,用于记录逆向遍历的位置
public ConcreteIterator(ConcreteStoreElement list) {
this.students = list.getObjects(); // 获取集合对象
cursor1 = 0; // 设置正向遍历游标的初始值
cursor2 = students.size() - 1; // 设置逆向遍历游标的初始值
}
public void next() {
if (cursor1 < students.size()) {
cursor1++;
}
}
public boolean isLast() {
return (cursor1 == students.size());
}
public void previous() {
if (cursor2 > -1) {
cursor2--;
}
}
public boolean isFirst() {
return (cursor2 == -1);
}
public Object getNextItem() {
return students.get(cursor1);
}
public Object getPreviousItem() {
return students.get(cursor2);
}
}
好,迭代器的原理就讲到这儿啦,下一篇我们来解析一下迭代器在jdk源码中是如何应用的。
本文若有不足之处,还请大家多多指正。如果这篇文章帮助到您,请您点赞支持。若有疑问,请小伙伴们私信我,或者评论区留言。谢谢大家。