用来解决上述问题的一个合理的解决方案就是迭代器模式。那么什么是迭代器模式呢?
(1)迭代器模式定义
所谓聚合是:指一组对象的组合结构,比如:Java中的集合、数组等。
(2)应用迭代器模式来解决的思路
仔细分析上面的问题,要以一个统一的方式来访问内部实现不同的聚合对象,那么首先就需要把这个统一的访问方式定义出来,按照这个统一的访问方式定义出来的接口,在迭代器模式中对应的就是Iterator接口。
迭代器迭代的是具体的聚合对象,那么不同的聚合对象就应该有不同的迭代器,为了让迭代器以一个统一的方式来操作聚合对象,因此给所有的聚合对象抽象出一个公共的父类,让它提供操作聚合对象的公共接口,这个抽象的公共父类在迭代器模式中对应的就是Aggregate对象。
接下来就该考虑如何创建迭代器了,由于迭代器和相应的聚合对象紧密相关,因此让具体的聚合对象来负责创建相应的迭代器对象。
迭代器模式的结构如图14.1所示:
图14.1 迭代器模式结构示意图
Iterator:
迭代器接口。定义访问和遍历元素的接口。
ConcreteIterator:
具体的迭代器实现对象。实现对聚合对象的遍历,并跟踪遍历时的当前位置。
Aggregate:
聚合对象。定义创建相应迭代器对象的接口。
ConcreteAggregate:
具体聚合对象。实现创建相应的迭代器对象。
(1)先来看看迭代器接口的定义,示例代码如下:
/** * 迭代器接口,定义访问和遍历元素的操作 */ public interface Iterator { /** * 移动到聚合对象的第一个位置 */ public void first(); /** * 移动到聚合对象的下一个位置 */ public void next(); /** * 判断是否已经移动到聚合对象的最后一个位置 * @return true表示已经移动到聚合对象的最后一个位置, * false表示还没有移动到聚合对象的最后一个位置 */ public boolean isDone(); /** * 获取迭代的当前元素 * @return 迭代的当前元素 */ public Object currentItem(); } |
(2)接下来看看具体的迭代器实现示意,示例代码如下:
/** * 具体迭代器实现对象,示意的是聚合对象为数组的迭代器 * 不同的聚合对象相应的迭代器实现是不一样的 */ public class ConcreteIterator implements Iterator { /** * 持有被迭代的具体的聚合对象 */ private ConcreteAggregate aggregate; /** * 内部索引,记录当前迭代到的索引位置。 * -1表示刚开始的时候,迭代器指向聚合对象第一个对象之前 */ private int index = -1; /** * 构造方法,传入被迭代的具体的聚合对象 * @param aggregate 被迭代的具体的聚合对象 */ public ConcreteIterator(ConcreteAggregate aggregate) { this.aggregate = aggregate; }
public void first(){ index = 0; } public void next(){ if(index < this.aggregate.size()){ index = index + 1; } } public boolean isDone(){ if(index == this.aggregate.size()){ return true; } return false; } public Object currentItem(){ return this.aggregate.get(index); } } |
(3)再来看看聚合对象的定义,示例代码如下:
/** * 聚合对象的接口,定义创建相应迭代器对象的接口 */ public abstract class Aggregate { /** * 工厂方法,创建相应迭代器对象的接口 * @return 相应迭代器对象的接口 */ public abstract Iterator createIterator(); } |
(4)接下来看看具体的聚合对象的实现,这里示意的是数组,示例代码如下:
/** * 具体的聚合对象,实现创建相应迭代器对象的功能 */ public class ConcreteAggregate extends Aggregate { /** * 示意,表示聚合对象具体的内容 */ private String[] ss = null;
/** * 构造方法,传入聚合对象具体的内容 * @param ss 聚合对象具体的内容 */ public ConcreteAggregate(String[] ss){ this.ss = ss; }
public Iterator createIterator() { //实现创建Iterator的工厂方法 return new ConcreteIterator(this); } /** * 获取索引所对应的元素 * @param index 索引 * @return 索引所对应的元素 */ public Object get(int index){ Object retObj = null; if(index < ss.length){ retObj = ss[index]; } return retObj; } /** * 获取聚合对象的大小 * @return 聚合对象的大小 */ public int size(){ return this.ss.length; } } |
(5)最后来看看如何使用这个聚合对象和迭代器对象,示例代码如下:
public class Client { /** * 示意方法,使用迭代器的功能。 * 这里示意使用迭代器来迭代聚合对象 */ public void someOperation(){ String[] names = {"张三","李四","王五"}; //创建聚合对象 Aggregate aggregate = new ConcreteAggregate(names); //循环输出聚合对象中的值 Iterator it = aggregate.createIterator(); //首先设置迭代器到第一个元素 it.first(); while(!it.isDone()){ //取出当前的元素来 Object obj = it.currentItem(); System.out.println("the obj=="+obj); //如果还没有迭代到最后,那么就向下迭代一个 it.next(); } } public static void main(String[] args) { //可以简单的测试一下 Client client = new Client(); client.someOperation(); } } |
要使用迭代器模式来实现示例,先来看看已有的两个工资系统现在的情况,然后再根据前面学习的迭代器模式来改造。
1:已有的系统
(1)首先是有一个已经统一了的工资描述模型,为了演示简单,这里只留下最基本的字段,描述一下支付工资的人员、支付的工资数额,其它的包括时间等都不描述了;同时为了后面调试方便,实现了toString方法。示例代码如下:
/** * 工资描述模型对象 */ public class PayModel { /** * 支付工资的人员 */ private String userName; /** * 支付的工资数额 */ private double pay; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public double getPay() { return pay; } public void setPay(double pay) { this.pay = pay; } public String toString(){ return "userName="+userName+",pay="+pay; } } |
(2)客户方已有的工资管理系统中的工资管理类,内部是通过List来管理的,简单的示例代码如下:
/** * 客户方已有的工资管理对象 */ public class PayManager{ /** * 聚合对象,这里是Java的集合对象 */ private List list = new ArrayList(); /** * 获取工资列表 * @return 工资列表 */ public List getPayList(){ return list; } /** * 计算工资,其实应该有很多参数,为了演示从简 */ public void calcPay(){ //计算工资,并把工资信息填充到工资列表里面 //为了测试,做点数据进去 PayModel pm1 = new PayModel(); pm1.setPay(3800); pm1.setUserName("张三");
PayModel pm2 = new PayModel(); pm2.setPay(5800); pm2.setUserName("李四");
list.add(pm1); list.add(pm2); } } |
(3)客户方收购的那家公司的工资管理系统中的工资管理类,内部是通过数组来管理的,简单的示例代码如下
/** * 被客户方收购的那个公司的工资管理类 */ public class SalaryManager{ /** * 用数组管理 */ private PayModel[] pms = null; /** * 获取工资列表 * @return 工资列表 */ public PayModel[] getPays(){ return pms; } /** * 计算工资,其实应该有很多参数,为了演示从简 */ public void calcSalary(){ //计算工资,并把工资信息填充到工资列表里面 //为了测试,做点数据进去 PayModel pm1 = new PayModel(); pm1.setPay(2200); pm1.setUserName("王五");
PayModel pm2 = new PayModel(); pm2.setPay(3600); pm2.setUserName("赵六");
pms = new PayModel[2]; pms[0] = pm1; pms[1] = pm2; } } |
(4)如果此时从外部来访问这两个工资列表,外部要采用不同的访问方式,一个是访问数组,一个是访问集合对象,示例代码如下:
public class Client { public static void main(String[] args) { //访问集团的工资列表 PayManager payManager= new PayManager(); //先计算再获取 payManager.calcPay(); Collection payList = payManager.getPayList(); Iterator it = payList.iterator(); System.out.println("集团工资列表:"); while(it.hasNext()){ PayModel pm = (PayModel)it.next(); System.out.println(pm); }
//访问新收购公司的工资列表 SalaryManager salaryManager = new SalaryManager(); //先计算再获取 salaryManager.calcSalary(); PayModel[] pms = salaryManager.getPays(); System.out.println("新收购的公司工资列表:"); for(int i=0;i<pms.length;i++){ System.out.println(pms[i]); } } } |
仔细查看框住的代码,会发现它们的访问方式是完全不一样的。
运行结果如下:
集团工资列表: userName=张三,pay=3800.0 userName=李四,pay=5800.0 新收购的公司工资列表: userName=王五,pay=2200.0 userName=赵六,pay=3600.0 |
2:统一访问聚合的接口
要使用迭代器模式来整合访问上面两个聚合对象,那就需要先定义出抽象的聚合对象和迭代器接口来,然后再提供相应的实现。
使用迭代器模式实现示例的结构如图14.2所示:
图14.2 使用迭代器模式实现示例的结构示意图
(1)为了让客户端能够以一个统一的方式进行访问,最好想的方式就是为它们定义一个统一的接口,都通过统一的接口来访问就简单了。这个示例用的Iterator跟模式的示例代码是一样的,这里就不去注释了,示例代码如下:
public interface Iterator { public void first(); public void next(); public boolean isDone(); public Object currentItem(); } |
(2)定义好了统一的接口,那就得分别实现这个接口。一个是List实现的,一个是数组实现的,先看数组实现的访问吧,示例代码如下:
/** * 用来实现访问数组的迭代接口 */ public class ArrayIteratorImpl implements Iterator{ /** * 用来存放被迭代的聚合对象 */ private SalaryManager aggregate = null; /** * 用来记录当前迭代到的位置索引 * -1表示刚开始的时候,迭代器指向聚合对象第一个对象之前 */ private int index = -1;
public ArrayIteratorImpl(SalaryManager aggregate){ this.aggregate = aggregate; } public void first(){ index = 0; } public void next(){ if(index < this.aggregate.size()){ index = index + 1; } } public boolean isDone(){ if(index == this.aggregate.size()){ return true; } return false; } public Object currentItem(){ return this.aggregate.get(index); } } |
为了让客户端能以统一的方式访问数据,所以对集合也提供一个对接口Iterator的实现,示例代码如下:
/** * 用来实现访问Collection集合的迭代接口,为了外部统一访问方式 */ public class CollectionIteratorImpl implements Iterator{ /** * 用来存放被迭代的聚合对象 */ private PayManager aggregate = null; /** * 用来记录当前迭代到的位置索引 * -1表示刚开始的时候,迭代器指向聚合对象第一个对象之前 */ private int index = -1;
public CollectionIteratorImpl(PayManager aggregate){ this.aggregate = aggregate; } public void first(){ index = 0; } public void next(){ if(index < this.aggregate.size()){ index = index + 1; } } public boolean isDone(){ if(index == this.aggregate.size()){ return true; } return false; } public Object currentItem(){ return this.aggregate.get(index); } } |
(3)获取访问聚合的接口
定义好了统一的访问聚合的接口,也分别实现了这个接口,新的问题是,在客户端要如何才能获取这个访问聚合的接口呢?而且还要以统一的方式来获取。
一个简单的方案就是定义一个获取访问聚合的接口的接口,客户端先通过这个接口来获取访问聚合的接口,然后再访问聚合对象。示例代码如下:
public abstract class Aggregate { /** * 工厂方法,创建相应迭代器对象的接口 * @return 相应迭代器对象的接口 */ public abstract Iterator createIterator(); } |
然后让具体的聚合对象PayManger和SalaryManager来继承这个抽象类,提供分别访问它们的访问聚合的接口。
修改PayManager对象,添加createIterator方法的实现,另外再添加迭代器回调聚合对象的方法,一个方法是获取聚合对象的大小,一个方法是根据索引获取聚合对象中的元素,示例代码如下:
public class PayManager extends Aggregate{ public Iterator createIterator(){ return new CollectionIteratorImpl(this); } public Object get(int index){ Object retObj = null; if(index < this.list.size()){ retObj = this.list.get(index); } return retObj; } public int size(){ return this.list.size(); }
} |
同理修改SalaryManager对象,示例代码如下:
public class SalaryManager extends Aggregate{ public Iterator createIterator(){ return new ArrayIteratorImpl(this); } public Object get(int index){ Object retObj = null; if(index < pms.length){ retObj = pms[index]; } return retObj; } public int size(){ return this.pms.length; }
} |
(4)统一访问的客户端
下面就来看看客户端,如何通过迭代器接口来访问聚合对象,为了显示是统一的访问,干脆把通过访问聚合的接口来访问聚合对象的功能独立成一个方法。虽然是访问不同的聚合对象,但是都调用这个方法去访问。示例代码如下:
public class Client { public static void main(String[] args) { //访问集团的工资列表 PayManager payManager= new PayManager(); //先计算再获取 payManager.calcPay(); System.out.println("集团工资列表:"); test(payManager.createIterator());
//访问新收购公司的工资列表 SalaryManager salaryManager = new SalaryManager(); //先计算再获取 salaryManager.calcSalary(); System.out.println("新收购的公司工资列表:"); test(salaryManager.createIterator()); }
/** * 测试通过访问聚合对象的迭代器,是否能正常访问聚合对象 * @param it 聚合对象的迭代器 */ private static void test(Iterator it){ //循环输出聚合对象中的值 //首先设置迭代器到第一个元素 it.first(); while(!it.isDone()){ //取出当前的元素来 Object obj = it.currentItem(); System.out.println("the obj=="+obj); //如果还没有迭代到最后,那么就向下迭代一个 it.next(); } } } |
运行一下客户端,测试看看效果。
小提示:
估计有些朋友看到这里,会觉得上面的实现特麻烦,会认为“Java里面就有Iterator接口,而且Java集合框架中的聚合对象也大都实现了Iterator接口的功能,还有必要像上面这么做吗?”
其实这么做,是为了让大家看到迭代器模式的全貌,后面会讲到用Java中的迭代器来实现。另外,有些时候还是需要自己来扩展和实现迭代器模式的,所以还是应该先独立学习迭代器模式。
(5)迭代器示例小结
如同前面示例,提供了一个统一访问聚合对象的接口,通过这个接口就可以顺序的访问聚合对象的元素,对于客户端而言,只是面向这个接口在访问,根本不知道聚合对象内部的表示方法。
事实上,前面的例子故意做了一个集合类型的聚合对象和一个数组类型的聚合对象,但是从客户端来看,访问聚合的代码是完全一样的,根本看不出任何的差别,也看不出到底聚合对象内部是什么类型。
---------------------------------------------------------------------------
私塾在线学习网原创内容 跟着cc学设计系列 之 研磨设计模式
研磨设计讨论群【252780326】
原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/5334.html】
---------------------------------------------------------------------------