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)迭代器示例小結
如同前面示例,提供了一個統一訪問聚合對象的接口,通過這個接口就可以順序的訪問聚合對象的元素,對於客戶端而言,只是面向這個接口在訪問,根本不知道聚合對象內部的表示方法。
事實上,前面的例子故意做了一個集合類型的聚合對象和一個數組類型的聚合對象,但是從客戶端來看,訪問聚合的代碼是完全一樣的,根本看不出任何的差別,也看不出到底聚合對象內部是什麼類型。