14.3 模式講解
14.3.1 認識迭代器模式
(1)迭代器模式的功能
迭代器模式的功能主要在於提供對聚合對象的迭代訪問。迭代器就圍繞着這個“訪問”做文章,延伸出很多的功能來。比如:
- 以不同的方式遍歷聚合對象,比如向前、向後等
- 對同一個聚合同時進行多個遍歷
- 以不同的遍歷策略來遍歷聚合,比如是否需要過濾等
- 多態迭代,含義是:爲不同的聚合結構,提供統一的迭代接口,也就是說通過一個迭代接口可以訪問不同的聚合結構,這就叫做多態迭代。上面的示例就已經實現了多態迭代,事實上,標準的迭代模式實現基本上都是支持多態迭代的。
但是請注意:多態迭代可能會帶來類型安全的問題,可以考慮使用泛型。
(2)迭代器模式的關鍵思想
聚合對象的類型很多,如果對聚合對象的迭代訪問跟聚合對象本身融合在一起的話,會嚴重影響到聚合對象的可擴展性和可維護性。
因此迭代器模式的關鍵思想就是把對聚合對象的遍歷和訪問從聚合對象中分離出來,放入到單獨的迭代器中,這樣聚合對象會變得簡單一些;而且迭代器和聚合對象可以獨立的變化和發展,會大大加強系統的靈活性。
(3)內部迭代器和外部迭代器
所謂內部迭代器,指的是由迭代器自己來控制迭代下一個元素的步驟,客戶端無法干預,因此,如果想要在迭代的過程中完成工作的話,客戶端就需要把操作傳給迭代器,迭代器在迭代的時候會在每個元素上執行這個操作,類似於Java的回調機制。
所謂外部迭代器,指的是由客戶端來控制迭代下一個元素的步驟,像前面的示例一樣,客戶端必須顯示的調用next來迭代下一個元素。
總體來說外部迭代器比內部迭代器要靈活一些,因此我們常見的實現多屬於外部迭代器,前面的例子也是實現的外部迭代器。
(4)Java中最簡單的統一訪問聚合的方式
如果只是想要使用一種統一的訪問方式來訪問聚合對象,在Java中有更簡單的方式,簡單到幾乎什麼都不用做,利用Java 5以上版本本身的特性即可。
但是請注意,這只是從訪問形式上一致了,但是也暴露了聚合的內部實現,因此並不能算是標準迭代器模式的實現,但是從某種意義上說,可以算是隱含的實現了部分迭代器模式的功能。
那麼怎麼做呢?
爲了簡單,讓我們回到沒有添加任何迭代器模式的情況下。很簡單,只要讓聚合對象中的結合實現範型即可,示例如下:
public class PayManager{ private List<PayModel> list = new ArrayList<PayModel>(); /** * 獲取工資列表 * @return 工資列表 */ public List<PayModel> getPayList(){ return list; }
} |
這樣一來,客戶端的代碼就可以改成使用增強的for循環來實現了,對於數組、範型的集合都可以採用一樣的方法來實現了,從代碼層面上看,就算是統一了訪問聚合的方式了,修改後的客戶端代碼如下:
public class Client { public static void main(String[] args) { //訪問集團的工資列表 PayManager payManager= new PayManager(); //先計算再獲取 payManager.calcPay();
Collection<PayModel> payList = payManager.getPayList(); System.out.println("集團工資列表:");
// Iterator it = payList.iterator(); // while(it.hasNext()){ // PayModel pm = (PayModel)it.next(); // System.out.println(pm); // }
for(PayModel pm : payList){ 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]); // } for(PayModel pm : pms){ System.out.println(pm); } } } |
14.3.2 使用Java的迭代器
大家都知道,在java.util包裏面有一個Iterator的接口,在Java中實現迭代器模式是非常簡單的,而且java的集合框架中的聚合對象,基本上都是提供了迭代器的。
下面就來把前面的例子改成用Java中的迭代器實現,一起來看看有些什麼改變。
- 不再需要自己實現的Iterator接口,直接實現java.util.Iterator接口就可以了,所有使用自己實現的Iterator接口的地方都需要修改過來
- Java中Iterator接口跟前面自己定義的接口相比,需要實現的方法是不一樣的
- 集合已經提供了Iterator,那麼CollectionIteratorImpl類就不需要了,直接去掉
好了,還是一起來看看代碼吧。
(1)PayModel類沒有任何變化,就不示例了
(2)抽象的Aggregate類就是把創建迭代器方法返回的類型換成java中的Iterator了。示例代碼如下:
import java.util.Iterator; public abstract class Aggregate { public abstract Iterator createIterator(); } |
(3)原來的ArrayIteratorImpl類,實現的接口改變了,實現代碼也需要跟着改變,示例代碼如下:
/** * 用來實現訪問數組的迭代接口 */ public class ArrayIteratorImpl implements Iterator{ /** * 用來存放被迭代的聚合對象 */ private SalaryManager aggregate = null; /** * 用來記錄當前迭代到的位置索引 */ private int index = 0; public ArrayIteratorImpl(SalaryManager aggregate){ this.aggregate = aggregate; }
public boolean hasNext() { //判斷是否還有下一個元素 if(aggregate!=null && index<aggregate.size()){ return true; } return false; } public Object next() { Object retObj = null; if(hasNext()){ retObj = aggregate.get(index); //每取走一個值,就把已訪問索引加1 index++; } return retObj; } public void remove() { //暫時可以不實現 } } |
(4)對於PayManager類,在實現創建迭代器的方法上發生了改變,不再使用自己實現的迭代器,改用java的集合框架實現的迭代器了。示例代碼如下:
public class PayManager extends Aggregate{ private List<PayModel> list = new ArrayList<PayModel>(); public List<PayModel> 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); } public Iterator createIterator() { return list.iterator(); } } |
(5)對於SalaryManager類,除了創建迭代器方法返回的類型改變外,其它都沒有改變,還是用ArrayIteratorImpl來實現迭代器。
(6)接下來寫個客戶端來測試看看,示例代碼如下:
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){ while(it.hasNext()){ PayModel pm = (PayModel)it.next(); System.out.println(pm); } } } |
很明顯改用Java的Iterator來實現,比自己全部重新去做,還是要簡單一些的。
14.3.3 帶迭代策略的迭代器
由於迭代器模式把聚合對象和訪問聚合的機制實現了分離,所以可以在迭代器上實現不同的迭代策略,最爲典型的就是實現過濾功能的迭代器。
在實際開發中,對於經常被訪問的一些數據可以使用緩存,把這些數據存放在內存中。但是不同的業務功能需要訪問的數據是不同的,還有不同的業務訪問權限能訪問的數據也是不同的,對於這種情況,就可以使用實現過濾功能的迭代器,讓不同功能使用不同的迭代器來訪問。當然,這種情況也可以結合策略模式來實現。
在實現過濾功能的迭代器中,又有兩種常見的需要過濾的情況,一種是對數據進行整條過濾,比如只能查看自己部門的數據;另外一種情況是對數據進行部分過濾,比如某些人不能查看工資數據。
帶迭代策略的迭代器實現的一個基本思路,就是先把聚合對象的聚合數據獲取到,並存儲到迭代器裏面來,這樣迭代器就可以按照不同的策略來迭代數據了。
1:帶迭代策略的迭代器示例
沿用上一個例子,來修改ArrayIteratorImpl來簡單的示意一下,不去考慮複雜的算法,大致的修改有:
- 原來是持有聚合對象的,現在直接把這個聚合對象的內容取出來存放到迭代器裏面,也就是迭代的時候,直接在迭代器裏面來獲取具體的聚合對象的元素,這樣纔好控制迭代的數據
- 在迭代器的具體實現上加入過濾的功能
示例代碼如下:
/** * 用來實現訪問數組的迭代接口,加入了迭代策略 */ public class ArrayIteratorImpl implements Iterator{ /** * 用來存放被迭代的數組 */ private PayModel[] pms = null; /** * 用來記錄當前迭代到的位置索引 */ private int index = 0;
public ArrayIteratorImpl(SalaryManager aggregate){ //在這裏先對聚合對象的數據進行過濾,比如工資必須在3000以下 Collection<PayModel> tempCol = new ArrayList<PayModel>(); for(PayModel pm : aggregate.getPays()){ if(pm.getPay() < 3000){ tempCol.add(pm); } } //然後把符合要求的數據存放到用來迭代的數組 this.pms = new PayModel[tempCol.size()]; int i=0; for(PayModel pm : tempCol){ this.pms[i] = pm; i++; } } public boolean hasNext() { //判斷是否還有下一個元素 if(pms!=null && index<=(pms.length-1)){ return true; } return false; } public Object next() { Object retObj = null; if(hasNext()){ retObj = pms[index]; //每取走一個值,就把已訪問索引加1 index++; } //在這裏對要返回的數據進行過濾,比如不讓查看工資數據 ((PayModel)retObj).setPay(0.0);
return retObj; } public void remove() { //暫時可以不實現 } } |
2:誰定義遍歷算法的問題
在實現迭代器模式的時候,一個常見的問題就是:誰來定義遍歷算法?其實帶策略的迭代器講述的也是這個問題。
在迭代器模式的實現中,常見有兩個地方可以來定義遍歷算法,一個就是聚合對象本身,另外一個就是迭代器負責遍歷算法。
在聚合對象本身定義遍歷的算法這種情況下,通常會在遍歷過程中,用迭代器來存儲當前迭代的狀態,這種迭代器被稱爲遊標,因爲它僅用來指示當前的位置。比如在14.2.4裏面示例的迭代器就屬於這種情況。
在迭代器裏面定義遍歷算法,會易於在相同的聚合上使用不同的迭代算法,同時也易於在不同的聚合上重用相同的算法。比如上面帶策略的迭代器的示例,迭代器把需要迭代的數據從聚合對象中取出並存放到自己對象裏面,然後再迭代自己的數據,這樣一來,除了剛開始創建迭代器的時候需要訪問聚合對象外,真正迭代過程已經跟聚合對象無關了。
當然,在迭代器裏面定義遍歷算法,如果實現遍歷算法的時候需要訪問聚合對象的私有變量,那麼將遍歷算法放入迭代器中會破壞聚合對象的封裝性。
至於究竟使用哪一種方式,要具體問題具體分析。
14.3.4 雙向迭代器
所謂雙向迭代器的意思就是:可以同時向前和向後遍歷數據的迭代器。
在Java util包中的ListIterator接口就是一個雙向迭代器的示例,當然自己實現雙向迭代器也非常容易,只要在自己的Iterator接口中添加向前的判斷和向前獲取值的方法,然後在實現中實現即可。
延續前面14.2.4的示例,來自己實現雙向迭代器,相同的部分就不去示範了,只演示不同的地方,先看看新的迭代器接口,示例代碼如下:
/** * 迭代器接口,定義訪問和遍歷元素的操作,實現雙向迭代 */ public interface Iterator { public void first(); public void next(); public boolean isDone(); public Object currentItem(); /** * 判斷是否爲第一個元素 * @return 如果爲第一個元素,返回true,否則返回false */ public boolean isFirst(); /** * 移動到聚合對象的上一個位置 */ public void previous(); } |
有了新的迭代器接口,也應該有新的實現。示例代碼如下:
/** * 用來實現訪問數組的雙向迭代接口 */ public class ArrayIteratorImpl implements Iterator{ private SalaryManager aggregate = null; 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); }
public boolean isFirst(){ if(index==0){ return true; } return false; } public void previous(){ if(index > 0 ){ index = index - 1; } } } |
基本實現完了,寫個客戶端來享受一下雙向迭代的樂趣。由於這個實現要考慮同時控制向前和向後迭代取值,而控制當前索引的是同一個值,因此在獲取向前取值得時候,要先把已訪問索引減去1,然後再取值,這個跟向後取值是反過來的,注意一下。示例代碼如下:
public class Client { public static void main(String[] args) { //訪問新收購公司的工資列表 SalaryManager salaryManager = new SalaryManager(); //先計算再獲取 salaryManager.calcSalary();
//得到雙向迭代器 Iterator it = salaryManager.createIterator(); //首先設置迭代器到第一個元素 it.first();
//先next一個 if(!it.isDone()){ PayModel pm = (PayModel)it.currentItem(); System.out.println("next1 == "+pm); //向下迭代一個 it.next(); } //然後previous一個 if(!it.isFirst()){ //向前迭代一個 it.previous(); PayModel pm = (PayModel)it.currentItem(); System.out.println("previous1 == "+pm); } //再next一個 if(!it.isDone()){ PayModel pm = (PayModel)it.currentItem(); System.out.println("next2 == "+pm); //向下迭代一個 it.next(); } //繼續next一個 if(!it.isDone()){ PayModel pm = (PayModel)it.currentItem(); System.out.println("next3 == "+pm); //向下迭代一個 it.next(); } //然後previous一個 if(!it.isFirst()){ //向前迭代一個 it.previous(); PayModel pm = (PayModel)it.currentItem(); System.out.println("previous2 == "+pm); }
} } |
上面的示例故意先向後取值,然後再向前取值,這樣反覆才能看出雙向迭代器的效果。運行的結果如下:
next1 == userName=王五,pay=2200.0 previous1 == userName=王五,pay=2200.0 next2 == userName=王五,pay=2200.0 next3 == userName=趙六,pay=3600.0 previous2 == userName=趙六,pay=3600.0 |
可能有些人會疑惑:爲什麼next1和previous1取出來的值是一樣的呢?
這是因爲現在是順序迭代,當next顯示第一條的時候,內部索引已經指向第二條了,所以這個時候再previous向前一條的時候,數據就是第一條數據了。
再仔細看上面的結果,發現這個時候繼續next數據時,數據還是第一條數據,同理,剛纔previous向前一條的時候,內部索引已經指向第一條之前了。
14.3.5 迭代器模式的優缺點
l 更好的封裝性
迭代器模式可以讓你訪問一個聚合對象的內容,而無需暴露該聚合對象的內部表示,從而提高聚合對象的封裝性
l 可以以不同的遍歷方式來遍歷一個聚合
使用迭代器模式,使得聚合對象的內容和具體的迭代算法分離開,這樣就可以通過使用不同的迭代器的實例,就可以使用不同的遍歷方式來遍歷一個聚合對象了,比如上面示例的帶迭代策略的迭代器。
l 迭代器簡化了聚合的接口
有了迭代器的接口,那麼聚合本身就不需要再定義這些接口了,從而簡化了聚合的接口定義。
l 簡化客戶端調用
迭代器爲遍歷不同的聚合對象提供了一個統一的接口,使得客戶端遍歷聚合對象的內容變得更簡單
l 同一個聚合上可以有多個遍歷。
每個迭代器保持它自己的遍歷狀態,比如前面實現中的迭代索引位置,因此可以對同一個聚合對象同時進行多個遍歷。
14.3.6 思考迭代器模式
1:迭代器模式的本質
迭代器模式的本質:控制訪問聚合對象中的元素。
迭代器能實現“無需暴露聚合對象的內部實現,就能夠訪問到聚合對象中各個元素”的功能,看起來其本質應該是“透明訪問聚合對象中的元素”。
但仔細思考一下,除了透明外,迭代器就沒有別的功能了嗎?很明顯還有其它的功能,前面也講到了一些,比如“帶迭代策略的迭代器”。那麼綜合來看,迭代器模式的本質應該是“控制訪問聚合對象中的元素”,而非單純的“透明”,事實上,“透明”訪問也是“控制訪問”的一種情況。
認識這個本質,對於識別和變形使用迭代器模式很有幫助。大家想想,現在的迭代模式默認的都是向前或者向後獲取一個值,也就是說都是單步迭代,那麼,如果想要控制一次迭代多條怎麼辦呢?
這個在實際開發中是很有用的,比如在實際開發中很常用的翻頁功能的實現,常見的翻頁功能有如下幾種實現方式:
(1)純數據庫實現
依靠SQL提供的功能實現翻頁,用戶每次請求翻頁的數據,就會到數據庫中獲取相應的數據
(2)純內存實現
就是一次性從數據庫中把需要的所有數據都取出來放到內存中,然後用戶請求翻頁時,從內存中獲取相應的數據
(3)上面兩種方式各有優缺點:
- 第一種方案明顯是時間換空間的策略,每次獲取翻頁的數據都要訪問數據庫,運行速度相對比較慢,而且很耗數據庫資源,但是節省內存空間。
- 而第二種方案是典型的空間換時間,每次是直接從內存中獲取翻頁的數據,運行速度快,但是很耗內存。
在實際開發中,小型系統一般採用第一種方案,基本沒有單獨採用第二種方案的,因爲內存實在是太寶貴了,中大型的系統一般是把兩個方案結合起來,綜合利用它們的優點,而又規避它們的缺點,從而更好的實現翻頁的功能。
(4)純數據庫實現 + 純內存實現
思路是這樣的:如果每頁顯示10條記錄,根據判斷,用戶很少翻到10頁過後,那好了,第一次訪問的時候,就一次性從數據庫中獲取前10頁的數據,也就是100條記錄,把這100條記錄放在內存裏面。
這樣一來,當用戶在前10頁內進行翻頁操作的時候,就不用再訪問數據庫了,而是直接從內存中獲取數據,這樣速度就快了。
當用戶想要獲取第11頁的數據,這個時候纔會再次訪問數據庫,對於這個時候到底獲取多少頁的數據,簡單的處理就是繼續獲取10頁的數據,比較好的方式就是根據訪問統計進行衰減訪問,比如折半獲取,也就是第一次訪問數據庫獲取10頁的數據,那麼第二次就只獲取5頁,如此操作直到一次從數據庫中獲取一頁的數據。這也符合正常規律,因爲越到後面,被用戶翻頁到的機會也就越小了。
對於翻頁的迭代,後面給大家一個簡單的示例。
2:何時選用迭代器模式
建議在如下情況中,選用迭代器模式:
- 如果你希望提供訪問一個聚合對象的內容,但是又不想暴露它的內部表示的時候,可以使用迭代器模式來提供迭代器接口,從而讓客戶端只是通過迭代器的接口來訪問聚合對象,而無需關心聚合對象內部實現。
- 如果你希望有多種遍歷方式可以訪問聚合對象,可以使用迭代器模式
- 如果你希望爲遍歷不同的聚合對象提供一個統一的接口,可以使用迭代器模式
14.3.7 翻頁迭代
在上面講到的翻頁實現機制中,只要使用到內存來緩存數據,就涉及到翻頁迭代的實現。簡單點說,就是一次迭代,會要求迭代取出一頁的數據,而不是一條數據。
其實實現翻頁迭代也很簡單,主要是把原來一次迭代一條數據的接口,都修改成一次迭代一頁的數據就可以。在具體的實現上,又分成順序翻頁迭代器和隨機翻頁迭代器。
1:順序翻頁迭代器示例
(1)先看看迭代器接口的定義,示例代碼如下:
/** * 定義翻頁訪問聚合元素的迭代接口 */ public interface AggregationIterator { /** * 判斷是否還有下一個元素,無所謂是否夠一頁的數據, * 因爲最後哪怕只有一條數據,也是要算一頁的 * @return 如果有下一個元素,返回true,沒有下一個元素就返回false */ public boolean hasNext(); /** * 取出下面幾個元素 * @param num 需要獲取的記錄條數 * @return 下面幾個元素 */ public Collection next(int num); /** * 判斷是否還有上一個元素,無所謂是否夠一頁的數據, * 因爲最後哪怕只有一條數據,也是要算一頁的 * @return 如果有上一個元素,返回true,沒有上一個元素就返回false */ public boolean hasPrevious(); /** * 取出上面幾個元素 * @param num 需要獲取的記錄條數 * @return 上面幾個元素 */ public Collection previous(int num); } |
(2)PayModel跟前面的示例是一樣的,這裏就不去贅述了
(3)接下來看看SalaryManager的實現,有如下改變:
- 不用再實現獲取聚合對象大小和根據索引獲取聚合對象中的元素的功能了
- 在準備測試數據的時候,多準備幾條,好看出翻頁的效果
示例代碼如下:
/** * 被客戶方收購的那個公司的工資管理類 */ public class SalaryManager{ private PayModel[] pms = null; 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("趙六");
PayModel pm3 = new PayModel(); pm3.setPay(2200); pm3.setUserName("王五二號");
PayModel pm4 = new PayModel(); pm4.setPay(3600); pm4.setUserName("趙六二號");
PayModel pm5 = new PayModel(); pm5.setPay(2200); pm5.setUserName("王五三號");
pms = new PayModel[5]; pms[0] = pm1; pms[1] = pm2; pms[2] = pm3; pms[3] = pm4; pms[4] = pm5; } public AggregationIterator createIterator() { return new ArrayIteratorImpl(this); } } |
(4)然後再看看如何實現迭代器接口,示例代碼如下:
/**
* 用來實現翻頁訪問聚合元素的迭代接口 public class ArrayIteratorImpl implements AggregationIterator{ /** * 用來存放被迭代的數組 */ private PayModel[] pms = null; /** * 用來記錄當前迭代到的位置索引 */ private int index = 0; public ArrayIteratorImpl(SalaryManager aggregate){ this.pms = aggregate.getPays(); } public boolean hasNext() { //判斷是否還有下一個元素 if(pms!=null && index<=(pms.length-1)){ return true; } return false; } public boolean hasPrevious() { if(pms!=null && index > 0){ return true; } return false; } public Collection next(int num) { Collection col = new ArrayList(); int count=0; while(hasNext() && count<num){ col.add(pms[index]); //每取走一個值,就把已訪問索引加1 index++; count++; } return col; } public Collection previous(int num){ Collection col = new ArrayList(); int count=0; //簡單的實現就是把索引退回去num個,然後再取值。 //但事實上這種實現是有可能多退回去數據的,比如:已經到了最後一頁, //而且最後一頁的數據不夠一頁的數據,那麼退回去num個索引就退多了 //爲了示例的簡潔性,這裏就不去處理了 index = index - num; while(hasPrevious() && count<num){ col.add(pms[index]); index ++; count++; } return col; } } |
(5)寫個客戶端測試看看,示例代碼如下:
public class Client { public static void main(String[] args) { //訪問新收購公司的工資列表,先計算再獲取 SalaryManager salaryManager = new SalaryManager(); salaryManager.calcSalary(); //得到翻頁迭代器 AggregationIterator it = salaryManager.createIterator();
//獲取第一頁,每頁顯示2條 Collection col = it.next(2); System.out.println("第一頁數據:"); print(col); //獲取第二頁,每頁顯示2條 Collection col2 = it.next(2); System.out.println("第二頁數據:"); print(col2);
//向前一頁,也就是再次獲取第二頁 Collection col3 = it.previous(2); System.out.println("再次獲取第二頁數據:"); print(col3); } private static void print(Collection col){ Iterator it = col.iterator(); while(it.hasNext()){ Object obj = it.next(); System.out.println(obj); } } } |
運行的結果如下:
第一頁數據: userName=王五,pay=2200.0 userName=趙六,pay=3600.0 第二頁數據: userName=王五二號,pay=2200.0 userName=趙六二號,pay=3600.0 再次獲取第二頁數據: userName=王五二號,pay=2200.0 userName=趙六二號,pay=3600.0 |
仍然是順序迭代的,也就是獲取完第二頁數據,內部索引就指向後面了,這個時候再運行向前一頁,取的就還是第二頁的數據了。
2:隨機翻頁迭代器示例
估計看到這裏,有些朋友會想,實際應用中,用戶怎麼會這麼老實,按照順序訪問,通常情況都是隨意的訪問頁數,比如看了第一頁可能就直接點第三頁了,看完第三頁他又想看第一頁。
這就需要隨機翻頁迭代器了,也就是可以指定頁面號和每頁顯示的數據來訪問數據的迭代器,下面來看看示例。
(1)修改迭代接口的方法,不需要再有向前和向後的方法,取而代之的是指定頁面號和每頁顯示的數據來訪問的方法,示例代碼如下:
/** * 定義隨機翻頁訪問聚合元素的迭代接口 */ public interface AggregationIterator { /** * 判斷是否還有下一個元素,無所謂是否夠一頁的數據, * 因爲最後哪怕只有一條數據,也是要算一頁的 * @return 如果有下一個元素,返回true,沒有下一個元素就返回false */ public boolean hasNext(); /** * 判斷是否還有上一個元素,無所謂是否夠一頁的數據, * 因爲最後哪怕只有一條數據,也是要算一頁的 * @return 如果有上一個元素,返回true,沒有上一個元素就返回false */ public boolean hasPrevious(); /** * 取出指定頁數的數據 * @param pageNum 要獲取的頁數 * @param pageShow 每頁顯示的數據條數 * @return 指定頁數的數據 */ public Collection getPage(int pageNum,int pageShow); } |
(2)定義了接口,看看具體的實現,示例代碼如下:
/** * 用來實現隨機翻頁訪問聚合元素的迭代接口 */ public class ArrayIteratorImpl implements AggregationIterator{ /** * 用來存放被迭代的數組 */ private PayModel[] pms = null; /** * 用來記錄當前迭代到的位置索引 */ private int index = 0; public ArrayIteratorImpl(SalaryManager aggregate){ this.pms = aggregate.getPays(); } public boolean hasNext() { //判斷是否還有下一個元素 if(pms!=null && index<=(pms.length-1)){ return true; } return false; } public boolean hasPrevious() { if(pms!=null && index > 0){ return true; } return false; } public Collection getPage(int pageNum,int pageShow){ Collection col = new ArrayList(); //需要在這裏先計算需要獲取的數據的開始條數和結束條數 int start = (pageNum-1)*pageShow; int end = start + pageShow-1; //控制start的邊界,最小是0 if(start < 0){ start = 0; } //控制end的邊界,最大是數組的最大索引 if(end > this.pms.length-1){ end = this.pms.length - 1; } //每次取值都是從頭開始循環,所以設置index爲0 index = 0; while(hasNext() && index<=end){ if(index >= start){ col.add(pms[index]); } //把已訪問索引加1 index++; } return col; } } |
(3)寫個客戶端,測試看看,是否能實現隨機的翻頁,示例代碼如下:
public class Client { public static void main(String[] args) { //訪問新收購公司的工資列表 SalaryManager salaryManager = new SalaryManager(); //先計算再獲取 salaryManager.calcSalary(); //得到翻頁迭代器 AggregationIterator it = salaryManager.createIterator();
//獲取第一頁,每頁顯示2條 Collection col = it.getPage(1,2); System.out.println("第一頁數據:"); print(col); //獲取第二頁,每頁顯示2條 Collection col2 = it.getPage(2,2); System.out.println("第二頁數據:"); print(col2); //再次獲取第一頁 Collection col3 = it.getPage(1,2); System.out.println("再次獲取第一頁數據:"); print(col3); //獲取第三頁 Collection col4 = it.getPage(3,2); System.out.println("獲取第三頁數據:"); print(col4); } private static void print(Collection col){ Iterator it = col.iterator(); while(it.hasNext()){ Object obj = it.next(); System.out.println(obj); } } } |
測試的結果如下:
第一頁數據: userName=王五,pay=2200.0 userName=趙六,pay=3600.0 第二頁數據: userName=王五二號,pay=2200.0 userName=趙六二號,pay=3600.0 再次獲取第一頁數據: userName=王五,pay=2200.0 userName=趙六,pay=3600.0 獲取第三頁數據: userName=王五三號,pay=2200.0 |
14.3.8 相關模式
l 迭代器模式和組合模式
這兩個模式可以組合使用。
組合模式是一種遞歸的對象結構,在枚舉某個組合對象的子對象的時候,通常會使用迭代器模式。
l 迭代器模式和工廠方法模式
這兩個模式可以組合使用。
在聚合對象創建迭代器的時候,通常會採用工廠方法模式來實例化相應的迭代器對象。