2、17迭代器模式
定义
1)迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。(可以编写多态的代码与这些聚合搭配)。把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
2)当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就该考虑使用迭代器模式。
3)当需要对聚集有多种方式遍历时,就可以考虑使用迭代器模式。
4)为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
5)迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
6)简单地说,迭代器就类似于一个数据库中的游标,可以在一个容器内上下翻滚,遍历所有它需要查看的元素。
通用类图
1)Iterator抽象迭代器
负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(对应java的hasNext()方法)。
2)ConcreteIterator具体迭代器
具体迭代器角色要实现迭代器接口,完成容器元素的遍历。
3)Aggregate抽象容器
容器角色负责提供创建具体迭代器角色的接口,必然提供一个类型concreteIterator()这样的方法,在java中一般是iterator()方法。
有这个共同的接口供所有的聚合使用,这对客户代码是很方便的,将客户代码从集合对象的实现解耦了。
4)Concrete Aggregate具体容器
具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。
#在此”通用案例”中实现的是外部的迭代器,也就是说,客户通过调用next()取得下一个元素。而内部的迭代器则是由迭代器自己控制。在这种情况下,因为是由迭代器自行在元素之间游走,所以你必须告诉迭代器在游走的过程中,要做些什么事情,也就是说,你必须将操作传入给迭代器。因为客户无法控制遍历的过程,所以内部迭代器比外部迭代器更没有弹性。然而,某些人可能认为内部的迭代器比价容易使用,因为只需将操作告诉它,它就会帮你做完所有事情。
通用案例
import java.util.Vector;
/**
* 情景类
*/
public class Client1 {
public static void main(String[] args) {
//声明容器
Aggregate aggregate = new ConcreteAggregate();
//产生对象数据放进去
aggregate.add("abc");
aggregate.add("123");
//遍历一下
Iterator iterator = aggregate.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
/**
* 抽象迭代器
*/
interface Iterator{
//遍历到下一个元素
Object next();
//是否已经遍历到尾部
boolean hasNext();
//删除当前指向的元素
boolean remove();
}
/**
* 具体迭代器
* TODO 开发系统时,迭代器的删除方法应该完成两个逻辑:一是删除当前元素,二是当前游标指向下一个元素.
*/
class ConcreteIterator implements Iterator{
private Vector vector = new Vector();
//定义当前游标
private int cursor = 0;
public ConcreteIterator(Vector vector) {
this.vector = vector;
}
/**
* 遍历到下一个元素
* @return
*/
@Override
public Object next() {
Object result = null;
if(this.hasNext()){
result = this.vector.get(this.cursor++);
}
return result;
}
/**
* 是否已经遍历到尾部
* @return
*/
@Override
public boolean hasNext() {
if(this.cursor == this.vector.size()){
return false;
}else{
return true;
}
}
/**
* 删除当前指向的元素
* @return
*/
@Override
public boolean remove() {
this.vector.remove(this.cursor);
return true;
}
}
/**
* 抽象容器
*/
interface Aggregate{
//增加元素
void add(Object object);
//减少元素
void remove(Object object);
//迭代器对象:遍历所有的元素
Iterator iterator();
}
/**
* 具体容器
*/
class ConcreteAggregate implements Aggregate{
//容纳对象的容器
private Vector vector = new Vector();
/**
* 增加元素
* @param object
*/
@Override
public void add(Object object) {
this.vector.add(object);
}
/**
* 减少元素
* @param object
*/
@Override
public void remove(Object object) {
this.remove(object);
}
/**
* 迭代器对象:遍历所有的元素
* @return
*/
@Override
public Iterator iterator() {
return new ConcreteIterator(this.vector);
}
}
业务案例类图
业务案例代码
import java.util.ArrayList;
import java.util.Iterator;
/**
* 场景类
*/
public class Client2 {
public static void main(String[] args) {
IProject project = new Project();
project.add("project1",1,1000);
project.add("project2",2,2000);
project.add("project3",3,3000);
IProjectIterator projectIterator = project.iterator();
while(projectIterator.hasNext()){
IProject pro = (IProject) projectIterator.next();
System.out.println(pro.getProjectInfo());
}
}
}
/**
* 项目信息接口
*/
interface IProject{
//增加项目
void add(String name,int num,int cost);
//项目信息
String getProjectInfo();
//被遍历的对象
IProjectIterator iterator();
}
/**
* 项目信息
*/
class Project implements IProject{
//定义一个项目列表
private ArrayList<IProject> projectList = new ArrayList<>();
//项目名称
private String name = "";
//项目成员数量
private int num = 0;
//项目费用
private int cost = 0;
public Project() {
}
private Project(String name, int num, int cost) {
this.name = name;
this.num = num;
this.cost = cost;
}
@Override
public void add(String name, int num, int cost) {
this.projectList.add(new Project(name,num,cost));
}
@Override
public String getProjectInfo() {
String info = "";
info = info + "项目名称是:" + this.name;
info = info + "\t项目人数是:" + this.num;
info = info + "\t项目费用是:" + this.cost;
return info;
}
@Override
public IProjectIterator iterator() {
return new ProjectIterator(this.projectList);
}
}
/**
* 项目迭代器接口
*/
interface IProjectIterator extends Iterator{
}
/**
* 项目迭代器(封装了遍历)
*/
class ProjectIterator implements IProjectIterator{
//所有的项目都放在ArrayList
private ArrayList<IProject> projectList = new ArrayList<>();
private int currentItem = 0;
public ProjectIterator(ArrayList<IProject> projectList) {
this.projectList = projectList;
}
@Override
public boolean hasNext() {
//定义一个返回值
boolean b = true;
if(this.currentItem >= projectList.size() || this.projectList.get(this.currentItem) == null){
b = false;
}
return b;
}
@Override
public Object next() {
return this.projectList.get(this.currentItem++);
}
@Override
public void remove() {
//暂时没有使用到
}
}
项目名称是:project1 项目人数是:1 项目费用是:1000
项目名称是:project2 项目人数是:2 项目费用是:2000
项目名称是:project3 项目人数是:3 项目费用是:3000
将枚举适配到迭代器
实际问题
在遗留的代码中,这些代码暴露出枚举接口,但是我们又希望在新的代码中只使用迭代器。要解决这个问题,就需要构造一个适配器(参考此设计模式系列中的”适配器”章节)。
结构关系
设计适配器类图
设计适配器案例
import java.util.Enumeration;
import java.util.Iterator;
/**
* 场景类
*/
public class Client3 {
}
/**
* EnumerationIterator适配器
* TODO 将枚举适配成迭代器,适配器需要实现迭代器接口,适配器必须看起来就像是一个迭代器.
*/
class EnumerationIterator implements Iterator{
private Enumeration enumeration;
/**
* 利用组合的方式,将枚举结合进入适配器中,所以用一个实例变量记录枚举.
* @param enumeration
*/
public EnumerationIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}
/**
* 迭代器的hasNext()方法其实是委托给枚举的hasMoreElements()方法.
* @return
*/
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
/**
* 迭代器的next()方法其实是委托给枚举的nextElement()方法.
* @return
*/
@Override
public Object next() {
return enumeration.nextElement();
}
/**
* 不能支持迭代器的remove()方法.在这里了是抛出一个异常.同java.util.Iterator中的处理.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
迭代器与集合
Collection类图
1)Collection和Iterator的好处在于,每个Collection都知道如何创建自己的Iterator。只要调用ArrayList(迭代器会暗中调用ArrayList的get方法。)上的iterator(),就可以返回一个具体的Iterator,而你根本不需要知道或关系到底使用了哪个具体类,你知道使用它的Iterator接口就可以了。
2)面向接口编程,接口是对一个事物的描述,也就是说通过接口就知道这个事物有哪些方法,哪些属性。
Hashtable的用法
import java.util.Hashtable;
import java.util.Iterator;
/**
* 场景类
* TODO HashTable对于迭代器的支持是"间接"的.
*/
public class Client4 {
public static void main(String[] args) {
CafeMenu menu = new CafeMenu();
for (int i = 0; i < 5; i++) {
menu.addMenu("nameval-" + i,"descriptionval-" + i);
}
Iterator iterator = menu.createIterator();
while(iterator.hasNext()){
MenuItem menuItem = (MenuItem)iterator.next();
System.out.println(menuItem);
}
}
}
interface Menu{
Iterator createIterator();
}
class CafeMenu implements Menu{
private Hashtable<String,MenuItem> menuItems = new Hashtable();
public void addMenu(String name,String description){
MenuItem menuItem = new MenuItem(name,description);
menuItems.put(name,menuItem);
}
/**
* HashTable对于迭代器的支持是"间接"的.这个迭代器不是直接从HashTable取出,而是由HashTable的value取出.
* 这种处理方法不错:因为HashTable内部存储了两组对象,key和value.
* 如果想遍历value,当然是要先从HashTable取得value,然后再取得迭代器.
* 这里不是取得整个HashTable的迭代器,而是取得值的部分的迭代器.
* @return
*/
@Override
public Iterator createIterator() {
return menuItems.values().iterator();
}
}
class MenuItem{
private String name;
private String description;
public MenuItem(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "MenuItem{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}
#可以发现这里的输出是无序的
MenuItem{name='nameval-2', description='descriptionval-2'}
MenuItem{name='nameval-1', description='descriptionval-1'}
MenuItem{name='nameval-0', description='descriptionval-0'}
MenuItem{name='nameval-4', description='descriptionval-4'}
MenuItem{name='nameval-3', description='descriptionval-3'}
Java5的迭代器和集合
import java.util.ArrayList;
import java.util.List;
/**
* 场景类
*/
public class Client5 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add("val-" + i);
}
/**
* 这里使用的java5的泛型特性来确保for/in的类型安全.
* 在集合中的每个对象之间反复遍历.
* 每次到循环的最后,obj会被赋值为集合中的下一个元素.
*/
for(String str : list){
System.out.println(str);
}
}
}
单一职责与内聚
1)一个类应该只有一个引起变化的原因。当允许一个类不但要完成自己的事情(某种聚合),还同时要担负更多的责任(例如遍历)时,此时就给了这个类两个变化的原因。如果集合改变的话,那么这个类也需要跟着改变。如果遍历的方式改变的话,那么这个类也需要跟着改变。
2)类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。所以尽量要让每个类保持单一责任。
3)内聚,用来度量一个类或模块紧密地达到单一目的或责任。
4)当一个模块或一个类被设计成只支持一组相关的功能时,此时具有高内聚。反之,当设计成支持一组不相关的功能时,此时具有低内聚。
5)内聚是一个比单一责任原则更普遍的概念,但两者其实关系是很密切的。遵守这个原则的类很容易具有很高的凝聚力,而且比背负许多责任的低内聚类更容易维护。
其他
1)对于散列表这样的集合,元素之间没有明显的次序关系,怎么办?
迭代器意味着没有次序。只是取出所有的元素,并不表示取出元素的先后就代表元素的大小次序。对于迭代器来说,数据结构可以是有次序的,或是没有次序的,甚至数据可以是重复的。除非某个集合的文件有特殊说明,否则不可以对迭代器所取出的元素大小顺序做出假设。
2)迭代器写的”多态”代码
当写了一个需要以迭代器当做参数的方法时,其实就是在使用多态的迭代。也就是说,可以在不同的集合中游走,只要这个集合支持迭代器即可。不用在乎这个集合是如何被实现的,但依然可以编程在它内部的元素之间游走。
3)迭代器是为容器服务的。容器:能容纳对象的所有类型都可以称之为容器,例如Collection集合类型、Set类型等,迭代器模式就是为了解决遍历这些容器中的元素而诞生的。迭代器模式提供了遍历容器的方便性,容器只要管理增减元素就可以了,需要遍历时交由迭代器进行。
4)“集合”指的是”一群对象”。其存储方式可以是各式各样的数据结构,例如:列表、数组、散列表,无论用什么方式存储,一律可以视为是集合,有时候也被称为聚合。
标题:设计模式(十七)行为模式(对象行为型模式)迭代器模式-ITERATOR
作者:yazong
地址:https://blog.llyweb.com/articles/2020/08/09/1596975035882.html