行为模式---之--迭代子模式
迭代子(Iterator)模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个聚焦中的元素而不必暴露聚焦的内部表象。
为什么聚集需要迭代子?
聚焦对象必须提供适当的方法,允许客户端能够按照一个线性顺序遍历所有的元素对象,把元素对象提取出来或删除掉等 。一个使用聚焦的系统必然会使用这些方法操控聚焦对象,因而在使用聚焦的系统演化过程中,会出现两类问题:
1.迭代逻辑没有改变,但是需要将一种聚焦换成另一种聚焦。因为不同的聚焦具有不同的遍历接口,所以需要修改客户端代码,以便将已有的迭代调用换成新聚焦对象所要求的接口
2.聚焦不会改变,但是迭代方式需要改变。如原来只需要读取元素和删除元素,但现在需要增加新的元素。这时就只好修改聚焦对象,修改已有的遍历方法,或增加新的方法。
出现这种情况 是因为所涉及的聚焦设计不符合“开-闭”原则,也就是因为没有将不变的结构从系统中抽象出来,与可变成分分割,并将可变部分的各种实现封装起来。一个聪明的做法应当是使用更加抽象的处理方法,使得在进行迭代时,客户端根本无需要知道所使用的聚焦是哪个类型:当客户端需要使用全新的迭代逻辑时,只需要引进一个新的迭代子对象即可,根本无需修改聚焦对象本身。迭代子模式便 是这样的一个抽象化的概念。这一模式能做到这一点,是因为它将迭代逻辑封装到一个独立的迭代子对象中,从而与聚焦本身分割开。迭代子对象是对遍历的抽象化,不同的聚焦对象可以提供相同的迭代子对象,从而使客户端无需知道聚焦的底层结构。一个聚焦可以提供多个不同的迭代子对象,从而使得遍历逻辑的变化不会影响到聚焦对象本身。
从对变化的封装角度看,迭代子模式将访问聚焦元素的逻辑封装起来,并且使它独立于聚焦对象的封装。这就是提供了聚集存储逻辑与迭代逻辑独立演变的空间,增加了系统的可复用性。可以使系统具有在无需修改的情况下进行扩展的能力。从代码重构的角度看,迭代子在客户端和聚焦之间增加了一个中介层,从而使客户端与聚焦之间的通信从直接变成了间接。这样做的好处是缓冲了客户端的变化对聚焦的影响,以及聚焦的变化对客户端的影响。
迭代子模式中的角色:
1.抽象迭代子(Iterator)角色:此抽象角色定义出遍历元素所需要的接口
2.具体迭代子(ConcreteIterator)角色:此角色实现了Iterator接口,并保持迭代过程中的游标位置
3.聚焦(Aggregate)角色:此抽象角色给出创建迭代了(Iterator)对象的接口
4.具体聚焦(ConcreteAggregate)角色:实现了创建迭代子(Iterator)对象的接口,返回一个合适的具体迭代子实例。
5.客户端角色:持有对聚焦及其迭代子对象的引用,调用迭代子对象的迭代接口,也有可能通过迭代子操作聚焦元素的增加和删除。
白箱聚集与外禀迭代子:
一个白箱聚集向外界提供访问自己内部元素的接口(称遍历方法),从而使外禀迭代子可以通过聚集的遍历方法实现迭代功能。
因为迭代的逻辑是由聚焦对象本身提供的,所有这样的外禀迭代子角色往往仅仅保持迭代的游标位置。在这种实现中具体迭代子角色是一个外部类,而具体聚集角色提供遍历聚焦元素的接口。
外禀迭代子的意义:
既然白箱聚焦已经向外界提供了遍历方法,客户端可以自行迭代了,为什么还要应用迭代子模式,并创建一个迭代子对象进行迭代呢?迭代子对象和迭代模式会将迭代过程抽象化,将作为迭代消费者的客户端与迭代负责人的迭代子责任分隔开,使得两者可以独立演化。在聚集对象的种类发生变化,或者迭代方法发生变化时,迭代子作为中介层可以吸收变化的因素,而避免修改客户端或聚焦本身。
此外如果系统需要同时针对几个不同的聚集对象进行迭代,而这些聚集对象所提供的遍历方法有所不同时,使用迭代子模式和一个外界的迭代子对象是有意义的。具有同一迭代接口的不同迭代子对象处理具有不同遍历接口的聚焦对象,使得系统可以使用一个统一的迭代接口进行所有的迭代
示例代码:
1 public class WhiteIterator { 2 private Iterator it; 3 private Aggregate agg = new ConcreteAggregate(); 4 public void operation(){ 5 it = agg.createIterator(); 6 while(!it.isDone()){ 7 System.out.println(it.currentIterm()); 8 it.next(); 9 } 10 } 11 public static void main(String[] args) { 12 WhiteIterator wi = new WhiteIterator(); 13 wi.operation(); 14 } 15 16 } 17 18 //抽象聚集角色Aggregate 19 abstract class Aggregate{ 20 //工厂方法:返还一个迭代子对象 21 public Iterator createIterator(){ 22 return null; 23 } 24 } 25 //抽象迭代子角色 26 interface Iterator{ 27 //迭代方法:移动到第一个元素 28 void first(); 29 //迭代方法:移动到下一个元素 30 void next(); 31 //迭代方法:是否是最后一个元素 32 boolean isDone(); 33 //迭代方法:返回当前元素 34 Object currentIterm(); 35 } 36 //具体聚焦角色 37 class ConcreteAggregate extends Aggregate{ 38 private Object[] obj ={"one","two","three"}; 39 //工厂方法:返回一个迭代子对象 40 public Iterator createIterator(){ 41 return new ConcreteIterator(this); 42 } 43 //取值方法:向外界提供聚集元素 44 public Object getElement(int index){ 45 if(index<obj.length){ 46 return obj[index]; 47 }else{ 48 return null; 49 } 50 } 51 //取值方法向外界提供聚集的大小 52 public int size(){ 53 return obj.length; 54 } 55 } 56 57 //具体迭代子 58 class ConcreteIterator implements Iterator{ 59 private ConcreteAggregate agg; 60 private int index =0; 61 private int size =0; 62 63 //构造方法,接收一个具体聚集对象为参量,使之可以控制聚集对象 64 public ConcreteIterator(ConcreteAggregate agg){ 65 this.agg = agg; 66 size = agg.size(); 67 index =0; 68 } 69 // 70 71 @Override 72 public void first() { 73 index = 0; 74 } 75 76 @Override 77 public void next() { 78 if(index<size){ 79 index++; 80 } 81 } 82 83 @Override 84 public boolean isDone() { 85 return (index>=size); 86 } 87 88 @Override 89 public Object currentIterm() { 90 return agg.getElement(index); 91 } 92 93 }
黑箱聚集和内禀迭代子:
一个黑箱聚集不向外部提供遍历自己元素对象的接口,因此,这些元素对象只可以被聚焦内部成员访问。由于内禀迭代子恰好是聚焦内部的成员子类,因此,内禀迭代子对象是可以访问聚焦的元素的。
示例性实现里,具体聚焦类ConcreteAggregate含有一个内部成员类ConcreteIterator,也就是实现了抽象迭代子接口的具体迭代子类,同时聚集并不向外界提供访问自己内部元素的方法。
示例代码:
1 public class BlackIterator { 2 private Iterator it; 3 private Aggregate agg = new ConcreteAggregate(); 4 public void operation(){ 5 it = agg.createIterator(); 6 while(!it.isDone()){ 7 System.out.println(it.currentIterm()); 8 it.next(); 9 } 10 } 11 public static void main(String[] args) { 12 BlackIterator bi = new BlackIterator(); 13 bi.operation(); 14 } 15 } 16 //抽象聚焦角色 17 abstract class Aggregate{ 18 //工厂方法:返回一个迭代子对象 19 public abstract Iterator createIterator(); 20 } 21 //抽象迭代子 22 interface Iterator{ 23 //迭代方法:移动到第一个元素 24 void first(); 25 //迭代方法:移动到下一个元素 26 void next(); 27 //迭代方法:是否是最后一个元素 28 boolean isDone(); 29 //迭代方法:返回当前元素 30 Object currentIterm(); 31 } 32 //具体聚集角色 33 class ConcreteAggregate extends Aggregate{ 34 private Object[] obj = {"one","two","three"}; 35 36 @Override 37 public Iterator createIterator() { 38 39 return new ConcreteIterator(); 40 } 41 //内部成员类:具体迭代子类 42 private class ConcreteIterator implements Iterator{ 43 private int currentIndex =0; 44 45 @Override 46 public void first() { 47 currentIndex = 0; 48 } 49 50 @Override 51 public void next() { 52 if(currentIndex <obj.length){ 53 currentIndex++; 54 } 55 } 56 57 @Override 58 public boolean isDone() { 59 return currentIndex==obj.length; 60 } 61 62 @Override 63 public Object currentIterm() { 64 return obj[currentIndex]; 65 } 66 67 } 68 }
在什么情况下使用内禀迭代与外禀迭代子?
一个外禀迭代子往往仅存储一个游标,因此如果有几个客户端同时进行迭代的话,那么可以使用几个外禀迭代子对象,由每一个迭代子对象控制一个独立的游标。但是,外禀迭代子对象要求聚集对象向外界提供遍历方法,因此会破坏聚焦的封装。如果某一个客户端可以修改聚集元素的话,迭代会给出不自恰的结果,甚至影响到系统其他部分的稳定性,造成系统崩溃。
使用外禀迭代子的一个重要理由是它可以被几个不同的方法和对象共同享用和控制。使用内禀迭代子的优点是不破坏对聚集的封装。
迭代子模式的优点:
1.迭代子模式简化了聚集的界面,迭代子具备了一个遍历接口,这样的聚焦的接口就不必具备遍历接口
2.每一个聚焦对象都 可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态是彼此独立的。因此,一个聚集对象可以同时有几个迭代在进行中。
3.由于遍历算法被封装在迭代角色里,因此迭代的算法可以独立于聚焦角色变化。由于客户端拿到的是一个迭代子对象,因此,不必知道聚焦对象的类型,就可以读取和遍历聚焦对象。这样即使聚集对象的类型发生变化,也不会影响到客户端的遍历过程。
迭代子模式的缺点:
1.迭代子模式给客户端一个聚集被顺序化的错觉,因为大多数情况下聚集的元素并没有确定的顺序,但是迭代必须以一定线性顺序进行。如果客户端误以为顺序是聚集本身具有的特性而过度依赖于聚集元素的顺序,就会出现错误
2.迭代子模式给出的聚集元素没有类型特征。一般而言,迭代子给出的元素都是Object类型,因此,客户端必须具备这些元素类型的知识才能使用这些元素。