考慮這樣一個實際應用:整合工資表數據。html
這個項目的背景是這樣的,項目的客戶方收購了一家小公司,這家小公司有本身的工資系統,如今須要整合到客戶方已有的工資系統上。java
客戶方已有的工資系統,在內部是採用的List來記錄工資列表;而新收購的這家公司的工資系統,在內部是採用的數組來記錄工資列表;可是幸運的是,兩個系統用來描述工資的數據模型是差很少的。算法
要整合這兩個工資系統的工資數據,固然最簡單的方式是考慮直接把新收購的這家公司的工資系統,也改爲內部使用List來記錄工資列表,可是通過仔細查看源代碼,發現有不少的代碼跟這個數組相關,還有不少是比較重要的邏輯處理,好比計算工資等,所以只好做罷。數據庫
如今除了要把兩個工資系統整合起來外,老闆還但願可以經過決策輔助系統來統一查看工資數據,他不想看到兩份不一樣的工資表。那麼應該如何實現呢?設計模式
原本就算內部描述形式不同,只要不須要整合在一塊兒,兩個系統單獨輸出本身的工資表,是沒有什麼問題的。可是,老闆還但願可以以一個統一的方式來查看全部的工資數據,也就是說從外部看起來,兩個系統輸出的工資表應該是同樣的。數組
通過分析,要知足老闆的要求,並且要讓兩邊的系統改動都儘量的小的話,問題的核心就在於如何可以以一種統一的方式來提供工資數據給決策輔助系統,換句說來講就是:如何可以以一個統一的方式來訪問內部實現不一樣的聚合對象。緩存
用來解決上述問題的一個合理的解決方案就是迭代器模式。那麼什麼是迭代器模式呢?安全
(1)迭代器模式定義框架
所謂聚合是:指一組對象的組合結構,好比:Java中的集合、數組等。ide
(2)應用迭代器模式來解決的思路
仔細分析上面的問題,要以一個統一的方式來訪問內部實現不一樣的聚合對象,那麼首先就須要把這個統一的訪問方式定義出來,按照這個統一的訪問方式定義出來的接口,在迭代器模式中對應的就是Iterator接口。
迭代器迭代的是具體的聚合對象,那麼不一樣的聚合對象就應該有不一樣的迭代器,爲了讓迭代器以一個統一的方式來操做聚合對象,所以給全部的聚合對象抽象出一個公共的父類,讓它提供操做聚合對象的公共接口,這個抽象的公共父類在迭代器模式中對應的就是Aggregate對象。
接下來就該考慮如何建立迭代器了,因爲迭代器和相應的聚合對象緊密相關,所以讓具體的聚合對象來負責建立相應的迭代器對象。
迭代器模式的結構如圖14.1所示:
Iterator:
迭代器接口。定義訪問和遍歷元素的接口。
ConcreteIterator:
具體的迭代器實現對象。實現對聚合對象的遍歷,並跟蹤遍歷時的當前位置。
Aggregate:
聚合對象。定義建立相應迭代器對象的接口。
ConcreteAggregate:
具體聚合對象。實現建立相應的迭代器對象。
(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(); } }
要使用迭代器模式來實現示例,先來看看已有的兩個工資系統如今的狀況,而後再根據前面學習的迭代器模式來改造。
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所示:
(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)迭代器示例小結
如同前面示例,提供了一個統一訪問聚合對象的接口,經過這個接口就能夠順序的訪問聚合對象的元素,對於客戶端而言,只是面向這個接口在訪問,根本不知道聚合對象內部的表示方法。
事實上,前面的例子故意作了一個集合類型的聚合對象和一個數組類型的聚合對象,可是從客戶端來看,訪問聚合的代碼是徹底同樣的,根本看不出任何的差異,也看不出到底聚合對象內部是什麼類型。
(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); } } }
你們都知道,在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來實現,比本身所有從新去作,仍是要簡單一些的。
因爲迭代器模式把聚合對象和訪問聚合的機制實現了分離,因此能夠在迭代器上實現不一樣的迭代策略,最爲典型的就是實現過濾功能的迭代器。
在實際開發中,對於常常被訪問的一些數據可使用緩存,把這些數據存放在內存中。可是不一樣的業務功能須要訪問的數據是不一樣的,還有不一樣的業務訪問權限能訪問的數據也是不一樣的,對於這種狀況,就可使用實現過濾功能的迭代器,讓不一樣功能使用不一樣的迭代器來訪問。固然,這種狀況也能夠結合策略模式來實現。
在實現過濾功能的迭代器中,又有兩種常見的須要過濾的狀況,一種是對數據進行整條過濾,好比只能查看本身部門的數據;另一種狀況是對數據進行部分過濾,好比某些人不能查看工資數據。
帶迭代策略的迭代器實現的一個基本思路,就是先把聚合對象的聚合數據獲取到,並存儲到迭代器裏面來,這樣迭代器就能夠按照不一樣的策略來迭代數據了。
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裏面示例的迭代器就屬於這種狀況。
在迭代器裏面定義遍歷算法,會易於在相同的聚合上使用不一樣的迭代算法,同時也易於在不一樣的聚合上重用相同的算法。好比上面帶策略的迭代器的示例,迭代器把須要迭代的數據從聚合對象中取出並存放到本身對象裏面,而後再迭代本身的數據,這樣一來,除了剛開始建立迭代器的時候須要訪問聚合對象外,真正迭代過程已經跟聚合對象無關了。
固然,在迭代器裏面定義遍歷算法,若是實現遍歷算法的時候須要訪問聚合對象的私有變量,那麼將遍歷算法放入迭代器中會破壞聚合對象的封裝性。
至於究竟使用哪種方式,要具體問題具體分析。
所謂雙向迭代器的意思就是:能夠同時向前和向後遍歷數據的迭代器。
在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向前一條的時候,內部索引已經指向第一條以前了。
l 更好的封裝性
迭代器模式可讓你訪問一個聚合對象的內容,而無需暴露該聚合對象的內部表示,從而提升聚合對象的封裝性
l 能夠以不一樣的遍歷方式來遍歷一個聚合
使用迭代器模式,使得聚合對象的內容和具體的迭代算法分離開,這樣就能夠經過使用不一樣的迭代器的實例,就可使用不一樣的遍歷方式來遍歷一個聚合對象了,好比上面示例的帶迭代策略的迭代器。
l 迭代器簡化了聚合的接口
有了迭代器的接口,那麼聚合自己就不須要再定義這些接口了,從而簡化了聚合的接口定義。
l 簡化客戶端調用
迭代器爲遍歷不一樣的聚合對象提供了一個統一的接口,使得客戶端遍歷聚合對象的內容變得更簡單
l 同一個聚合上能夠有多個遍歷。
每一個迭代器保持它本身的遍歷狀態,好比前面實現中的迭代索引位置,所以能夠對同一個聚合對象同時進行多個遍歷。
1:迭代器模式的本質
迭代器模式的本質:控制訪問聚合對象中的元素。
迭代器能實現「無需暴露聚合對象的內部實現,就可以訪問到聚合對象中各個元素」的功能,看起來其本質應該是「透明訪問聚合對象中的元素」。
但仔細思考一下,除了透明外,迭代器就沒有別的功能了嗎?很明顯還有其它的功能,前面也講到了一些,好比「帶迭代策略的迭代器」。那麼綜合來看,迭代器模式的本質應該是「控制訪問聚合對象中的元素」,而非單純的「透明」,事實上,「透明」訪問也是「控制訪問」的一種狀況。
認識這個本質,對於識別和變形使用迭代器模式頗有幫助。你們想一想,如今的迭代模式默認的都是向前或者向後獲取一個值,也就是說都是單步迭代,那麼,若是想要控制一次迭代多條怎麼辦呢?
這個在實際開發中是頗有用的,好比在實際開發中很經常使用的翻頁功能的實現,常見的翻頁功能有以下幾種實現方式:
(1)純數據庫實現
依靠SQL提供的功能實現翻頁,用戶每次請求翻頁的數據,就會到數據庫中獲取相應的數據
(2)純內存實現
就是一次性從數據庫中把須要的全部數據都取出來放到內存中,而後用戶請求翻頁時,從內存中獲取相應的數據
(3)上面兩種方式各有優缺點:
第一種方案明顯是時間換空間的策略,每次獲取翻頁的數據都要訪問數據庫,運行速度相對比較慢,並且很耗數據庫資源,可是節省內存空間。
而第二種方案是典型的空間換時間,每次是直接從內存中獲取翻頁的數據,運行速度快,可是很耗內存。
在實際開發中,小型系統通常採用第一種方案,基本沒有單獨採用第二種方案的,由於內存實在是太寶貴了,中大型的系統通常是把兩個方案結合起來,綜合利用它們的優勢,而又規避它們的缺點,從而更好的實現翻頁的功能。
(4)純數據庫實現 + 純內存實現
思路是這樣的:若是每頁顯示10條記錄,根據判斷,用戶不多翻到10頁事後,那好了,第一次訪問的時候,就一次性從數據庫中獲取前10頁的數據,也就是100條記錄,把這100條記錄放在內存裏面。
這樣一來,當用戶在前10頁內進行翻頁操做的時候,就不用再訪問數據庫了,而是直接從內存中獲取數據,這樣速度就快了。
當用戶想要獲取第11頁的數據,這個時候纔會再次訪問數據庫,對於這個時候到底獲取多少頁的數據,簡單的處理就是繼續獲取10頁的數據,比較好的方式就是根據訪問統計進行衰減訪問,好比折半獲取,也就是第一次訪問數據庫獲取10頁的數據,那麼第二次就只獲取5頁,如此操做直到一次從數據庫中獲取一頁的數據。這也符合正常規律,由於越到後面,被用戶翻頁到的機會也就越小了。
對於翻頁的迭代,後面給你們一個簡單的示例。
2:什麼時候選用迭代器模式
建議在以下狀況中,選用迭代器模式:
若是你但願提供訪問一個聚合對象的內容,可是又不想暴露它的內部表示的時候,可使用迭代器模式來提供迭代器接口,從而讓客戶端只是經過迭代器的接口來訪問聚合對象,而無需關心聚合對象內部實現。
若是你但願有多種遍歷方式能夠訪問聚合對象,可使用迭代器模式
若是你但願爲遍歷不一樣的聚合對象提供一個統一的接口,可使用迭代器模式
在上面講到的翻頁實現機制中,只要使用到內存來緩存數據,就涉及到翻頁迭代的實現。簡單點說,就是一次迭代,會要求迭代取出一頁的數據,而不是一條數據。
其實實現翻頁迭代也很簡單,主要是把原來一次迭代一條數據的接口,都修改爲一次迭代一頁的數據就能夠。在具體的實現上,又分紅順序翻頁迭代器和隨機翻頁迭代器。
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
l 迭代器模式和組合模式
這兩個模式能夠組合使用。
組合模式是一種遞歸的對象結構,在枚舉某個組合對象的子對象的時候,一般會使用迭代器模式。
l 迭代器模式和工廠方法模式
這兩個模式能夠組合使用。
在聚合對象建立迭代器的時候,一般會採用工廠方法模式來實例化相應的迭代器對象。
轉載至:http://sishuok.com/forum/blogPost/list/5335.html
cc老師的設計模式是我目前看過最詳細最有實踐的教程。