迭代器模式

泪湿孤枕 提交于 2019-12-03 20:21:17
20.1 整理项目信息——苦差事周五下午, 我正在看技术网站, 第六感官发觉有人在身后, 扭头一看, 老大站在背后,我赶忙站起来。“王经理, 你找我? ”“哦, 在看技术呀。 有个事情找你谈一下, 你到我办公室来一下。 ”到老大办公室还没坐稳, 老大就开始发话了。“是这样, 刚刚我在看季报, 我们每个项目的支出费用都很高, 项目情况复杂, 人员情况也不简单, 我看着也有点糊涂, 你看, 这是我们现在还在开发或者维护的103个项目, 项目信息很乱, 很多是两年前的信息, 你能不能先把这些项目最新情况重新打印一份给我, 咱们好查查到底有什么问题。 ”老大说。“这个好办, 我马上去办!”我爽快地答复道。很快我设计了一个类图, 准备实施, 如图20-1所示。图20-1 项目信息类图简单得不能再简单的类图, 是个程序员都能实现。 我们来看看这个简单的东西, 先看接口, 如代码清单20-1所示。代码清单20-1 项目信息接口public interface IProject {//从老板这里看到的就是项目信息public String getProjectInfo();}定义了一个接口, 面向接口编程嘛, 当然要定义接口了, 然后看看实现类, 如代码清单20-2所示。代码清单20-2 项目信息的实现public class Project implements IProject {//项目名称private String name = "";//项目成员数量private int num = 0;//项目费用private int cost = 0;//定义一个构造函数, 把所有老板需要看到的信息存储起来public Project(String name,int num,int cost){//赋值到类的成员变量中this.name = name;this.num = num;this.cost=cost;}//得到项目的信息public String getProjectInfo() {String info = "";//获得项目的名称info = info+ "项目名称是: " + this.name;//获得项目人数info = info + "\t项目人数: "+ this.num;//项目费用info = info+ "\t 项目费用: "+ this.cost;return info;}}实现类也是极度简单, 通过构造函数把要显示的数据传递过来, 然后放到getProjectInfo中显示, 这太容易了! 然后我们老大要看看结果了, 如代码清单20-3所示。代码清单20-3 老大看报表的场景public class Boss {public static void main(String[] args) {//定义一个List, 存放所有的项目对象ArrayList<IProject> projectList = new ArrayList<IProject>();//增加星球大战项目projectList.add(new Project("星球大战项目",10,100000));//增加扭转时空项目projectList.add(new Project("扭转时空项目",100,10000000));//增加超人改造项目projectList.add(new Project("超人改造项目",10000,1000000000));//这边100个项目for(int i=4;i<104;i++){projectList.add(new Project("第"+i+"个项目",i*5,i*1000}//遍历一下ArrayList, 把所有的数据都取出for(IProject project:projectList){System.out.println(project.getProjectInfo());}}}然后看一下我们的运行结果, 如下所示:项目名称是: 星球大战项目 项目人数: 10 项目费用: 100000项目名称是: 扭转时空项目 项目人数: 100 项目费用: 10000000项目名称是: 超人改造项目 项目人数: 10000 项目费用: 1000000000项目名称是: 第4个项目 项目人数: 20 项目费用: 4000000项目名称是: 第5个项目 项目人数: 25 项目费用: 5000000. . .老大一看, 非常开心, 这么快就出结果了, 大大地把我夸奖了一番, 然后就去埋头研究那堆枯燥的报表了。 我回到座位上, 又看了一遍程序(心里很乐, 就又想看看自己的成果) , 想想(一日三省嘛) , 应该还有另外一种实现方式, 因为是遍历嘛, 让我想到的就是Java的迭代器接口java.util.iterator, 它的作用就是遍历Collection集合下的元素, 那我们的程序还可以有另外一种实现, 通过实现iterator接口来实现遍历, 先修正一下类图, 如图20-2所示。图20-2 增加迭代接口的类图看着是不是复杂了很多? 是的, 是有点复杂了, 是不是我们把简单的事情复杂化了? 请读者继续阅读下去, 我等会儿说明原因。 我们先分析一下我们的类图java.util.Iterator接口中声明了三个方法, 这是JDK定义的, ProjectIterator 实现该接口, 并且聚合了Project对象, 也就是把Project对象作为本对象的成员变量使用。 看类图还不是很清晰, 我们一起看一下代码, 先看IProject接口的改变, 如代码清单20-4所示。代码清单20-4 项目信息接口public interface IProject {//增加项目public void add(String name,int num,int cost);//从老板这里看到的就是项目信息public String getProjectInfo();//获得一个可以被遍历的对象public IProjectIterator iterator();}这里多了两个方法, 一个是add方法, 这个方法是增加项目, 也就是说产生了一个对象后, 直接使用add方法增加项目信息。 我们再来看其实现类, 如代码清单20-5所示。代码清单20-5 项目信息public class Project implements IProject {//定义一个项目列表, 说有的项目都放在这里private ArrayList<IProject> projectList = new ArrayList<IProject>();//项目名称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;}//增加项目public void add(String name,int num,int cost){this.projectList.add(new Project(name,num,cost));}//得到项目的信息public String getProjectInfo() {String info = "";//获得项目的名称info = info+ "项目名称是: " + this.name;//获得项目人数info = info + "\t项目人数: "+ this.num;//项目费用info = info+ "\t 项目费用: "+ this.cost;return info;}//产生一个遍历对象public IProjectIterator iterator(){return new ProjectIterator(this.projectList);}}通过构造函数, 传递了一个项目所必需的信息, 然后通过iterator()方法, 把所有项目都返回到一个迭代器中。 Iterator()方法看不懂不要紧, 继续向下阅读。 再看IProjectIterator接口, 如代码清单20-6所示。代码清单20-6 项目迭代器接口public interface IProjectIterator extends Iterator {}大家可能对该接口感觉很奇怪, 你定义的这个接口方法、 变量都没有, 有什么意义呢?有意义, 所有的Java书上都会说要面向接口编程, 你的接口是对一个事物的描述, 也就是说我通过接口就知道这个事物有哪些方法, 哪些属性, 我们这里的IProjectIterator是要建立一个指向Project类的迭代器, 目前暂时定义的就是一个通用的迭代器, 可能以后会增加IProjectIterator的一些属性或者方法。 当然了, 你也可以在实现类上实现两个接口, 一个是Iterator,一个是IProjectIterator(这时候, 这个接口就不用继承Iterator) , 杀猪杀尾巴, 各有各的杀法。 我的习惯是: 如果我要实现一个容器或者其他API提供接口时, 我一般都自己先写一个接口继承, 然后再继承自己写的接口, 保证自己的实现类只用实现自己写的接口(接口传递, 当然也要实现顶层的接口) , 程序阅读也清晰一些。 我们继续看迭代器的实现类, 如代码清单20-7所示。代码清单20-7 项目迭代器public class ProjectIterator implements IProjectIterator {//所有的项目都放在ArrayList中private ArrayList<IProject> projectList = new ArrayList<IProject>();private int currentItem = 0;//构造函数传入projectListpublic ProjectIterator(ArrayList<IProject> projectList){this.projectList = projectList;}//判断是否还有元素, 必须实现public boolean hasNext() {//定义一个返回值boolean b = true;if(this.currentItem>=projectList.size()||this.projectList.get(this.cb =false;}return b;}//取得下一个值public IProject next() {return (IProject)this.projectList.get(this.currentItem++);}//删除一个对象public void remove() {//暂时没有使用到}}细心的读者可能会从代码中发现一个问题, java.util.iterator接口中定义next()方法的返回值类型是E, 而你在ProjectIterator中返回值却是IProject, E和IProject有什么关系?E是JDK 1.5中定义的新类型: 元素(Element) , 是一个泛型符号, 表示一个类型, 具体什么类型是在实现或运行时决定, 总之它代表的是一种类型, 你在这个实现类中把它定义为ProjectIterator, 在另外一个实现类可以把它定义为String, 都没有问题。 它与Object这个类可是不同的, Object是所有类的父类, 随便一个类你都可以把它向上转型到Object类, 也只是因为它是所有类的父类, 它才是一个通用类, 而E是一个符号, 代表所有的类, 当然也代表Object了。都写完毕了, 看看我们的Boss类有多少改动, 如代码清单20-8所示。代码清单20-8 老板看报表public class Boss {public static void main(String[] args) {//定义一个List, 存放所有的项目对象IProject project = new Project();//增加星球大战项目project.add("星球大战项目ddddd",10,100000);//增加扭转时空项目project.add("扭转时空项目",100,10000000);//增加超人改造项目project.add("超人改造项目",10000,1000000000);//这边100个项目for(int i=4;i<104;i++){project.add("第"+i+"个项目",i*5,i*1000000);}//遍历一下ArrayList, 把所有的数据都取出IProjectIterator projectIterator = project.iterator();while(projectIterator.hasNext()){IProject p = (IProject)projectIterator.next();System.out.println(p.getProjectInfo());}}}运行结果如下所示:项目名称是: 星球大战项目 项目人数: 10 项目费用: 100000项目名称是: 扭转时空项目 项目人数: 100 项目费用: 10000000项目名称是: 超人改造项目 项目人数: 10000 项目费用: 1000000000项目名称是: 第4个项目 项目人数: 20 项目费用: 4000000项目名称是: 第5个项目 项目人数: 25 项目费用: 5000000. . . 运行结果完全相同, 但是上面的程序复杂性增加了不少, 难道我们退回到原始时代了吗? 非也, 非也, 只是我们回退到JDK 1.0.8版本的编程时代了, 我们使用一种新的设计模式——迭代器模式。20.2 迭代器模式的定义迭代器模式(Iterator Pattern) 目前已经是一个没落的模式, 基本上没人会单独写一个迭代器, 除非是产品性质的开发, 其定义如下:Provide a way to access the elements of an aggregate object sequentially without exposing itsunderlying representation.(它提供一种方法访问一个容器对象中各个元素, 而又不需暴露该对象的内部细节。 )迭代器是为容器服务的, 那什么是容器呢? 能容纳对象的所有类型都可以称之为容器, 例如Collection集合类型、 Set类型等, 迭代器模式就是为解决遍历这些容器中的元素而诞生的。 其通用类图, 如图20-3所示。图20-3 迭代器模式的通用类图迭代器模式提供了遍历容器的方便性, 容器只要管理增减元素就可以了, 需要遍历时交由迭代器进行。 迭代器模式正是由于使用得太频繁, 所以大家才会忽略, 我们来看看迭代器模式中的各个角色:● Iterator抽象迭代器抽象迭代器负责定义访问和遍历元素的接口, 而且基本上是有固定的3个方法: first()获得第一个元素, next()访问下一个元素, isDone()是否已经访问到底部(Java叫做hasNext()方法) 。● ConcreteIterator具体迭代器具体迭代器角色要实现迭代器接口, 完成容器元素的遍历。● Aggregate抽象容器容器角色负责提供创建具体迭代器角色的接口, 必然提供一个类似createIterator()这样的方法, 在Java中一般是iterator()方法。● Concrete Aggregate具体容器具体容器实现容器接口定义的方法, 创建出容纳迭代器的对象。我们来看迭代器模式的通用源代码, 先看抽象迭代器Iterator, 如代码清单20-9所示。代码清单20-9 抽象迭代器public interface Iterator {//遍历到下一个元素public Object next();//是否已经遍历到尾部public boolean hasNext();//删除当前指向的元素public boolean remove();}具体迭代器如代码清单20-10所示。代码清单20-10 具体迭代器public class ConcreteIterator implements Iterator {private Vector vector = new Vector();//定义当前游标public int cursor = 0;@SuppressWarnings("unchecked")public ConcreteIterator(Vector _vector){this.vector = _vector;}//判断是否到达尾部public boolean hasNext() {if(this.cursor == this.vector.size()){return false;}else{return true;}}//返回下一个元素public Object next() {Object result = null;if(this.hasNext()){result = this.vector.get(this.cursor++);}else{result = null;}return result;}//删除当前元素public boolean remove() {this.vector.remove(this.cursor);return true;}}注意 开发系统时, 迭代器的删除方法应该完成两个逻辑: 一是删除当前元素, 二是当前游标指向下一个元素。抽象容器如代码清单20-11所示。代码清单20-11 抽象容器public interface Aggregate {//是容器必然有元素的增加public void add(Object object);//减少元素public void remove(Object object);//由迭代器来遍历所有的元素public Iterator iterator();}具体容器如代码清单20-12所示。代码清单20-12 具体容器public class ConcreteAggregate implements Aggregate {//容纳对象的容器private Vector vector = new Vector();//增加一个元素public void add(Object object) {this.vector.add(object);}//返回迭代器对象public Iterator iterator() {return new ConcreteIterator(this.vector);}//删除一个元素public void remove(Object object) {this.remove(object);}}场景类如代码清单20-13所示。代码清单20-13 场景类public class Client {public static void main(String[] args) {//声明出容器Aggregate agg = new ConcreteAggregate();//产生对象数据放进去agg.add("abc");agg.add("aaa");agg.add("1234");//遍历一下Iterator iterator = agg.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}}简单地说, 迭代器就类似于一个数据库中的游标, 可以在一个容器内上下翻滚, 遍历所有它需要查看的元素。20.3 迭代器模式的应用我们在例子中使用了迭代器模式后为什么使原本简单的应用变得复杂起来了呢? 那是因为我们在简单的应用中使用了迭代器, 在哪? 请看代码清单20-3, 注意这段话: for(IProjectproject:projectList), 它为什么能够运行起来? 还不是因为ArrayList已经实现了iterator()方法,我们才能如此简单地应用。从JDK 1.2版本开始增加java.util.Iterator这个接口, 并逐步把Iterator应用到各个聚集类(Collection) 中, 我们来看JDK 1.5的API帮助文件, 你会看到有一个叫java.util.Iterable的接口, 看看有多少个接口继承了它:BeanContext,BeanContextServices,BlockingQueue<E>,Collection<E>,List<E>,Queue<E>,Set<E>,Sor再看看有它多少个实现类:AbstractCollection,AbstractList,AbstractQueue,AbstractSequentialList,AbstractSet,ArrayBlockingQue基本上我们经常使用的类都在这个表中了, 也正是因为Java把迭代器模式已经融入到基本API中了, 我们才能如此轻松、 便捷。我们再来看看Iterable接口。 java.util.Iterable接口只有一个方法: iterator(), 也就说, 通过iterator()这个方法去遍历聚集类中的所有方法或属性, 基本上现在所有的高级语言都有Iterator这个接口或者实现, Java已经把迭代器给我们准备好了, 我们再去写迭代器, 就有点多余了。 所以呀, 这个迭代器模式也有点没落了, 基本上很少有项目再独立写迭代器了, 直接使用Collection下的实现类就可以完美地解决问题。迭代器现在应用得越来越广泛了, 甚至已经成为一个最基础的工具。 一些大师级人物甚至建议把迭代器模式从23个模式中删除, 为什么呢? 就是因为现在它太普通了, 已经融入到各个语言和工具中了, 比如PHP中你能找到它的身影, Perl也有它的存在, 甚至是前台的页面技术AJAX也可以有它的出现(如在Struts2中就可以直接使用iterator) 。 基本上, 只要你不是在使用那些古董级(指版本号) 的编程语言的话, 都不用自己动手写迭代器。20.4 最佳实践如果你是做Java开发, 尽量不要自己写迭代器模式! 省省吧, 使用Java提供的Iterator一般就能满足你的要求了。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!