設計模式實戰-迭代器模式
1 概念

定義

型別
-
迭代器模式(Iterator Pattern)
目前已經是一個沒落的模式,基本上沒人會單獨寫一個迭代器,除非是產品性質的開發,其定義如下:
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一種方法訪問一個容器物件中各個元素,而又不需暴露該物件的內部細節。)
迭代器是為容器服務的,那什麼是容器呢? 能容納物件的所有型別都可以稱之為容器,例如Collection集合型別、Set型別等,迭代器模式就是為解決遍歷這些容器中的元素而誕生的

迭代器模式的通用類圖
迭代器模式提供了遍歷容器的方便性,容器只要管理增減元素就可以了,需要遍歷時交由迭代器進行
看看迭代器模式中的各個角色:
- Iterator抽象迭代器
抽象迭代器負責定義訪問和遍歷元素的介面,而且基本上是有固定的3個方法:- first()獲得第一個元素
- next()訪問下一個元素
- hasNext()是否已經訪問到底部
- ConcreteIterator具體迭代器
具體迭代器角色要實現迭代器介面,完成容器元素的遍歷。 - Aggregate抽象容器
容器角色負責提供建立具體迭代器角色的介面,必然提供一個類似createIterator()這樣的方法,在Java中一般是iterator()方法。 - Concrete Aggregate具體容器
具體容器實現容器介面定義的方法,創建出容納迭代器的物件。
我們來看迭代器模式的通用原始碼
- 抽象迭代器
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } /** * Performs the given action for each remaining element until all elements * have been processed or the action throws an exception.Actions are * performed in the order of iteration, if that order is specified. * Exceptions thrown by the action are relayed to the caller. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code *while (hasNext()) *action.accept(next()); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */ default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
- 具體迭代器
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; } }
開發系統時,迭代器的刪除方法應該完成兩個邏輯
-
刪除當前元素
-
當前遊標指向下一個元素
-
抽象容器
public interface Aggregate { //是容器必然有元素的增加 public void add(Object object); //減少元素 public void remove(Object object); //由迭代器來遍歷所有的元素 public Iterator iterator(); }
- 具體容器
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); } }
- 場景類
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()); } } }
簡單地說,迭代器就類似於一個數據庫中的遊標,可以在一個容器內上下翻滾,遍歷所有它需要檢視的元素。
2 適用場景

3 優點

4 缺點

相關模式

例項
現在還在開發或者維護的103個專案,專案資訊很亂,很多是兩年前的資訊,你能不能先把這些專案最新情況重新列印一份給我,咱們好查查到底有什麼問題

專案資訊類圖
簡單得不能再簡單的類圖,是個程式員都能實現。我們來看看這個簡單的東西
- 專案資訊介面
public interface IProject { //從老闆這裡看到的就是專案資訊 public String getProjectInfo(); }
- 專案資訊的實現
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](http://this.name/); //獲得專案人數 info = info + "\t專案人數: "+ this.num; //專案費用 info = info+ "\t 專案費用:"+ this.cost; return info; } }
通過建構函式把要顯示的資料傳遞過來,然後放到getProjectInfo中顯示
- 報表的場景
public class Boss { public static void main(String[] args) { //定義一個List,存放所有的專案物件 ArrayList projectList = new ArrayList(); //增加星球大戰專案 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*1000000)); } //遍歷一下ArrayList,把所有的資料都取出 for(IProject project:projectList){ System.out.println(project.getProjectInfo()); } } }
然後看一下我們的執行結果,如下所示:

又看了一遍程式,應該還有另外一種實現方式,因為是遍歷嘛,讓我想到的就是Java的迭代器介面java.util.iterator,它的作用就是遍歷Collection集合下的元素,那我們的程式還可以有另外一種實現,通過實現iterator介面來實現遍歷

增加迭代介面的類圖
看著是不是複雜了很多?是的,是有點複雜了,是不是我們把簡單的事情複雜化了?
我們先分析一下我們的類圖java.util.Iterator介面中聲明瞭三個方法,這是JDK定義的, ProjectIterator 實現該介面,並且聚合了Project物件,也就是把Project物件作為本物件的成員變數使用。看類圖還不是很清晰,我們一起看一下程式碼,先看IProject介面的改變
- 專案資訊介面
public interface IProject { //增加專案 public void add(String name,int num,int cost); //從老闆這裡看到的就是專案資訊 public String getProjectInfo(); //獲得一個可以被遍歷的物件 public IProjectIterator iterator(); }
這裡多了兩個方法,一個是add方法,這個方法是增加專案,也就是說產生了一個物件後,直接使用add方法增加專案資訊。我們再來看其實現類
- 專案資訊
public class Project implements IProject { //定義一個專案列表,說有的專案都放在這裡 private ArrayList 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](http://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](http://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介面
- 專案迭代器介面
public interface IProjectIterator extends Iterator { }
大家可能對該介面感覺很奇怪,你定義的這個介面方法、變數都沒有,有什麼意義呢?有意義,所有的Java書上都會說要面向介面程式設計,你的介面是對一個事物的描述,也就是說我通過介面就知道這個事物有哪些方法,哪些屬性,我們這裡的IProjectIterator是要建立一個指向Project類的迭代器,目前暫時定義的就是一個通用的迭代器,可能以後會增加IProjectIterator的一些屬性或者方法。當然了,你也可以在實現類上實現兩個介面,一個是Iterator,一個是IProjectIterator(這時候,這個介面就不用繼承Iterator),殺豬殺尾巴,各有各的殺法。
如果我要實現一個容器或者其他API提供介面時,我一般都自己先寫一個介面繼承,然後再繼承自己寫的介面,保證自己的實現類只用實現自己寫的介面(介面傳遞,當然也要實現頂層的介面)
我們繼續看迭代器的實現類
- 專案迭代器
public class ProjectIterator implements IProjectIterator { //所有的專案都放在ArrayList中 private ArrayList projectList = new ArrayList(); private int currentItem = 0; //建構函式傳入projectList public ProjectIterator(ArrayList projectList){ this.projectList = projectList; } //判斷是否還有元素,必須實現 public boolean hasNext() { //定義一個返回值 boolean b = true; if(this.currentItem>=projectList.size()||this.projectList.get(this.currentItem)==null){ b =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類有多少改動
- 老闆看報表
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()); } } }
執行結果如下所示
