研磨設計模式 之 迭代器模式(Iterator)2——跟著cc學設計系列
14.2 解決方案
14.2.1 迭代器模式來解決
用來解決上述問題的一個合理的解決方案就是迭代器模式。那麼什麼是迭代器模式呢?
(1)迭代器模式定義
所謂聚合是:指一組物件的組合結構,比如:Java中的集合、陣列等。
(2)應用迭代器模式來解決的思路
仔細分析上面的問題,要以一個統一的方式來訪問內部實現不同的聚合物件,那麼首先就需要把這個統一的訪問方式定義出來,按照這個統一的訪問方式定義出來的介面,在迭代器模式中對應的就是Iterator介面。
迭代器迭代的是具體的聚合物件,那麼不同的聚合物件就應該有不同的迭代器,為了讓迭代器以一個統一的方式來操作聚合物件,因此給所有的聚合物件抽象出一個公共的父類,讓它提供操作聚合物件的公共介面,這個抽象的公共父類在迭代器模式中對應的就是Aggregate物件。
接下來就該考慮如何建立迭代器了,由於迭代器和相應的聚合物件緊密相關,因此讓具體的聚合物件來負責建立相應的迭代器物件。
14.2.2 模式結構和說明
迭代器模式的結構如圖14.1所示:
圖14.1 迭代器模式結構示意圖
Iterator:
迭代器介面。定義訪問和遍歷元素的介面。
ConcreteIterator:
具體的迭代器實現物件。實現對聚合物件的遍歷,並跟蹤遍歷時的當前位置。
Aggregate:
聚合物件。定義建立相應迭代器物件的介面。
ConcreteAggregate:
具體聚合物件。實現建立相應的迭代器物件。
14.2.3 迭代器模式示例程式碼
(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(); } } |
14.2.4 使用迭代器模式來實現示例
要使用迭代器模式來實現示例,先來看看已有的兩個工資系統現在的情況,然後再根據前面學習的迭代器模式來改造。
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)迭代器示例小結
如同前面示例,提供了一個統一訪問聚合物件的介面,通過這個介面就可以順序的訪問聚合物件的元素,對於客戶端而言,只是面向這個介面在訪問,根本不知道聚合物件內部的表示方法。
事實上,前面的例子故意做了一個集合型別的聚合物件和一個數組型別的聚合物件,但是從客戶端來看,訪問聚合的程式碼是完全一樣的,根本看不出任何的差別,也看不出到底聚合物件內部是什麼型別。
---------------------------------------------------------------------------
研磨設計討論群【252780326】
---------------------------------------------------------------------------