1 場景問題#
1.1 工資表數(shù)據(jù)的整合##
考慮這樣一個實際應用:整合工資表數(shù)據(jù)翁逞。
這個項目的背景是這樣的庸队,項目的客戶方收購了一家小公司箩祥,這家小公司有自己的工資系統(tǒng)两芳,現(xiàn)在需要整合到客戶方已有的工資系統(tǒng)上撵孤。
客戶方已有的工資系統(tǒng)迈着,在內(nèi)部是采用的List來記錄工資列表;而新收購的這家公司的工資系統(tǒng)邪码,在內(nèi)部是采用的數(shù)組來記錄工資列表裕菠;但是幸運的是,兩個系統(tǒng)用來描述工資的數(shù)據(jù)模型是差不多的闭专。
要整合這兩個工資系統(tǒng)的工資數(shù)據(jù)奴潘,當然最簡單的方式是考慮直接把新收購的這家公司的工資系統(tǒng)旧烧,也改成內(nèi)部使用List來記錄工資列表,但是經(jīng)過仔細查看源代碼画髓,發(fā)現(xiàn)有很多的代碼跟這個數(shù)組相關(guān)掘剪,還有很多是比較重要的邏輯處理,比如計算工資等奈虾,因此只好作罷夺谁。
現(xiàn)在除了要把兩個工資系統(tǒng)整合起來外,老板還希望能夠通過決策輔助系統(tǒng)來統(tǒng)一查看工資數(shù)據(jù)肉微,他不想看到兩份不同的工資表匾鸥。那么應該如何實現(xiàn)呢?
1.2 有何問題##
本來就算內(nèi)部描述形式不一樣碉纳,只要不需要整合在一起勿负,兩個系統(tǒng)單獨輸出自己的工資表,是沒有什么問題的劳曹。但是奴愉,老板還希望能夠以一個統(tǒng)一的方式來查看所有的工資數(shù)據(jù),也就是說從外部看起來厚者,兩個系統(tǒng)輸出的工資表應該是一樣的躁劣。
經(jīng)過分析迫吐,要滿足老板的要求库菲,而且要讓兩邊的系統(tǒng)改動都盡可能的小的話,問題的核心就在于如何能夠以一種統(tǒng)一的方式來提供工資數(shù)據(jù)給決策輔助系統(tǒng)
志膀,換句說來說就是:如何能夠以一個統(tǒng)一的方式來訪問內(nèi)部實現(xiàn)不同的聚合對象
熙宇。
2 解決方案#
2.1 迭代器模式來解決##
用來解決上述問題的一個合理的解決方案就是迭代器模式。那么什么是迭代器模式呢溉浙?
- 迭代器模式定義
所謂聚合是:指一組對象的組合結(jié)構(gòu)烫止,比如:Java中的集合、數(shù)組等戳稽。
- 應用迭代器模式來解決的思路
仔細分析上面的問題馆蠕,要以一個統(tǒng)一的方式來訪問內(nèi)部實現(xiàn)不同的聚合對象
,那么首先就需要把這個統(tǒng)一的訪問方式定義出來惊奇,按照這個統(tǒng)一的訪問方式定義出來的接口互躬,在迭代器模式中對應的就是Iterator接口。
迭代器迭代的是具體的聚合對象颂郎,那么不同的聚合對象就應該有不同的迭代器
吼渡,為了讓迭代器以一個統(tǒng)一的方式來操作聚合對象,因此給所有的聚合對象抽象出一個公共的父類乓序,讓它提供操作聚合對象的公共接口寺酪,這個抽象的公共父類在迭代器模式中對應的就是Aggregate對象
坎背。
接下來就該考慮如何創(chuàng)建迭代器了,由于迭代器和相應的聚合對象緊密相關(guān)寄雀,因此讓具體的聚合對象來負責創(chuàng)建相應的迭代器對象得滤。
2.2 模式結(jié)構(gòu)和說明#
迭代器模式的結(jié)構(gòu)如圖所示:
Iterator:迭代器接口。定義訪問和遍歷元素的接口咙俩。
ConcreteIterator:具體的迭代器實現(xiàn)對象耿戚。實現(xiàn)對聚合對象的遍歷,并跟蹤遍歷時的當前位置阿趁。
Aggregate:聚合對象膜蛔。定義創(chuàng)建相應迭代器對象的接口。
ConcreteAggregate:具體聚合對象脖阵。實現(xiàn)創(chuàng)建相應的迭代器對象皂股。
2.3 迭代器模式示例代碼##
- 先來看看迭代器接口的定義,示例代碼如下:
/**
* 迭代器接口命黔,定義訪問和遍歷元素的操作
*/
public interface Iterator {
/**
* 移動到聚合對象的第一個位置
*/
public void first();
/**
* 移動到聚合對象的下一個位置
*/
public void next();
/**
* 判斷是否已經(jīng)移動到聚合對象的最后一個位置
* @return true表示已經(jīng)移動到聚合對象的最后一個位置呜呐,
* false表示還沒有移動到聚合對象的最后一個位置
*/
public boolean isDone();
/**
* 獲取迭代的當前元素
* @return 迭代的當前元素
*/
public Object currentItem();
}
- 接下來看看具體的迭代器實現(xiàn)示意,示例代碼如下:
/**
* 具體迭代器實現(xiàn)對象悍募,示意的是聚合對象為數(shù)組的迭代器
* 不同的聚合對象相應的迭代器實現(xiàn)是不一樣的
*/
public class ConcreteIterator implements Iterator {
/**
* 持有被迭代的具體的聚合對象
*/
private ConcreteAggregate aggregate;
/**
* 內(nèi)部索引蘑辑,記錄當前迭代到的索引位置。
* -1表示剛開始的時候坠宴,迭代器指向聚合對象第一個對象之前
*/
private int index = -1;
/**
* 構(gòu)造方法洋魂,傳入被迭代的具體的聚合對象
* @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);
}
}
- 再來看看聚合對象的定義,示例代碼如下:
/**
* 聚合對象的接口喜鼓,定義創(chuàng)建相應迭代器對象的接口
*/
public abstract class Aggregate {
/**
* 工廠方法副砍,創(chuàng)建相應迭代器對象的接口
* @return 相應迭代器對象的接口
*/
public abstract Iterator createIterator();
}
- 接下來看看具體的聚合對象的實現(xiàn),這里示意的是數(shù)組庄岖,示例代碼如下:
/**
* 具體的聚合對象豁翎,實現(xiàn)創(chuàng)建相應迭代器對象的功能
*/
public class ConcreteAggregate extends Aggregate {
/**
* 示意,表示聚合對象具體的內(nèi)容
*/
private String[] ss = null;
/**
* 構(gòu)造方法隅忿,傳入聚合對象具體的內(nèi)容
* @param ss 聚合對象具體的內(nèi)容
*/
public ConcreteAggregate(String[] ss){
this.ss = ss;
}
public Iterator createIterator() {
//實現(xiàn)創(chuàng)建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;
}
}
- 最后來看看如何使用這個聚合對象和迭代器對象心剥,示例代碼如下:
public class Client {
/**
* 示意方法,使用迭代器的功能背桐。
* 這里示意使用迭代器來迭代聚合對象
*/
public void someOperation(){
String[] names = {"張三","李四","王五"};
//創(chuàng)建聚合對象
Aggregate aggregate = new ConcreteAggregate(names);
//循環(huán)輸出聚合對象中的值
Iterator it = aggregate.createIterator();
//首先設(shè)置迭代器到第一個元素
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();
}
}
2.4 使用迭代器模式來實現(xiàn)示例##
要使用迭代器模式來實現(xiàn)示例,先來看看已有的兩個工資系統(tǒng)現(xiàn)在的情況牢撼,然后再根據(jù)前面學習的迭代器模式來改造匙隔。
- 已有的系統(tǒng)
(1)首先是有一個已經(jīng)統(tǒng)一了的工資描述模型,為了演示簡單,這里只留下最基本的字段纷责,描述一下支付工資的人員捍掺、支付的工資數(shù)額,其它的包括時間等都不描述了再膳;同時為了后面調(diào)試方便挺勿,實現(xiàn)了toString方法。示例代碼如下:
/**
* 工資描述模型對象
*/
public class PayModel {
/**
* 支付工資的人員
*/
private String userName;
/**
* 支付的工資數(shù)額
*/
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)客戶方已有的工資管理系統(tǒng)中的工資管理類喂柒,內(nèi)部是通過List來管理的不瓶,簡單的示例代碼如下:
/**
* 客戶方已有的工資管理對象
*/
public class PayManager{
/**
* 聚合對象,這里是Java的集合對象
*/
private List list = new ArrayList();
/**
* 獲取工資列表
* @return 工資列表
*/
public List getPayList(){
return list;
}
/**
* 計算工資灾杰,其實應該有很多參數(shù)蚊丐,為了演示從簡
*/
public void calcPay(){
//計算工資,并把工資信息填充到工資列表里面
//為了測試艳吠,做點數(shù)據(jù)進去
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)客戶方收購的那家公司的工資管理系統(tǒng)中的工資管理類麦备,內(nèi)部是通過數(shù)組來管理的,簡單的示例代碼如下:
/**
* 被客戶方收購的那個公司的工資管理類
*/
public class SalaryManager{
/**
* 用數(shù)組管理
*/
private PayModel[] pms = null;
/**
* 獲取工資列表
* @return 工資列表
*/
public PayModel[] getPays(){
return pms;
}
/**
* 計算工資昭娩,其實應該有很多參數(shù)凛篙,為了演示從簡
*/
public void calcSalary(){
//計算工資,并把工資信息填充到工資列表里面
//為了測試栏渺,做點數(shù)據(jù)進去
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)如果此時從外部來訪問這兩個工資列表呛梆,外部要采用不同的訪問方式,一個是訪問數(shù)組磕诊,一個是訪問集合對象填物,示例代碼如下:
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]);
}
}
}
仔細查看框住的代碼,會發(fā)現(xiàn)它們的訪問方式是完全不一樣的秀仲。運行結(jié)果如下:
集團工資列表:
userName=張三,pay=3800.0
userName=李四,pay=5800.0
新收購的公司工資列表:
userName=王五,pay=2200.0
userName=趙六,pay=3600.0
- 統(tǒng)一訪問聚合的接口
要使用迭代器模式來整合訪問上面兩個聚合對象融痛,那就需要先定義出抽象的聚合對象和迭代器接口來壶笼,然后再提供相應的實現(xiàn)
神僵。
使用迭代器模式實現(xiàn)示例的結(jié)構(gòu)如圖所示:
(1)為了讓客戶端能夠以一個統(tǒng)一的方式進行訪問,最好想的方式就是為它們定義一個統(tǒng)一的接口覆劈,都通過統(tǒng)一的接口來訪問就簡單了
保礼。這個示例用的Iterator跟模式的示例代碼是一樣的,這里就不去注釋了责语,示例代碼如下:
public interface Iterator {
public void first();
public void next();
public boolean isDone();
public Object currentItem();
}
(2)定義好了統(tǒng)一的接口炮障,那就得分別實現(xiàn)這個接口。一個是List實現(xiàn)的坤候,一個是數(shù)組實現(xiàn)的胁赢,先看數(shù)組實現(xiàn)的訪問吧,示例代碼如下:
/**
* 用來實現(xiàn)訪問數(shù)組的迭代接口
*/
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);
}
}
為了讓客戶端能以統(tǒng)一的方式訪問數(shù)據(jù)智末,所以對集合也提供一個對接口Iterator的實現(xiàn)谅摄,示例代碼如下:
/**
* 用來實現(xiàn)訪問Collection集合的迭代接口,為了外部統(tǒng)一訪問方式
*/
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)獲取訪問聚合的接口
定義好了統(tǒng)一的訪問聚合的接口送漠,也分別實現(xiàn)了這個接口,新的問題是周崭,在客戶端要如何才能獲取這個訪問聚合的接口呢偎血?而且還要以統(tǒng)一的方式來獲取电谣。
一個簡單的方案就是定義一個獲取訪問聚合的接口的接口,客戶端先通過這個接口來獲取訪問聚合的接口爷狈,然后再訪問聚合對象
。示例代碼如下:
public abstract class Aggregate {
/**
* 工廠方法裳擎,創(chuàng)建相應迭代器對象的接口
* @return 相應迭代器對象的接口
*/
public abstract Iterator createIterator();
}
然后讓具體的聚合對象PayManger和SalaryManager來繼承這個抽象類淆院,提供分別訪問它們的訪問聚合的接口。
修改PayManager對象句惯,添加createIterator方法的實現(xiàn)土辩,另外再添加迭代器回調(diào)聚合對象的方法,一個方法是獲取聚合對象的大小抢野,一個方法是根據(jù)索引獲取聚合對象中的元素拷淘,示例代碼如下:
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)統(tǒng)一訪問的客戶端
下面就來看看客戶端指孤,如何通過迭代器接口來訪問聚合對象启涯,為了顯示是統(tǒng)一的訪問,干脆把通過訪問聚合的接口來訪問聚合對象的功能獨立成一個方法恃轩。雖然是訪問不同的聚合對象结洼,但是都調(diào)用這個方法去訪問。示例代碼如下:
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){
//循環(huán)輸出聚合對象中的值
//首先設(shè)置迭代器到第一個元素
it.first();
while(!it.isDone()){
//取出當前的元素來
Object obj = it.currentItem();
System.out.println("the obj=="+obj);
//如果還沒有迭代到最后松忍,那么就向下迭代一個
it.next();
}
}
}
小提示:
估計有些朋友看到這里,會覺得上面的實現(xiàn)特麻煩筷厘,會認為“Java里面就有Iterator接口鸣峭,而且Java集合框架中的聚合對象也大都實現(xiàn)了Iterator接口的功能,還有必要像上面這么做嗎酥艳?”
其實這么做摊溶,是為了讓大家看到迭代器模式的全貌,后面會講到用Java中的迭代器來實現(xiàn)充石。另外莫换,有些時候還是需要自己來擴展和實現(xiàn)迭代器模式的,所以還是應該先獨立學習迭代器模式。
(5)迭代器示例小結(jié)
如同前面示例拉岁,提供了一個統(tǒng)一訪問聚合對象的接口溃列,通過這個接口就可以順序的訪問聚合對象的元素,對于客戶端而言膛薛,只是面向這個接口在訪問听隐,根本不知道聚合對象內(nèi)部的表示方法。
事實上哄啄,前面的例子故意做了一個集合類型的聚合對象和一個數(shù)組類型的聚合對象雅任,但是從客戶端來看,訪問聚合的代碼是完全一樣的咨跌,根本看不出任何的差別沪么,也看不出到底聚合對象內(nèi)部是什么類型。
3 模式講解#
3.1 認識迭代器模式##
- 迭代器模式的功能
迭代器模式的功能主要在于提供對聚合對象的迭代訪問
锌半。迭代器就圍繞著這個“訪問”做文章禽车,延伸出很多的功能來。比如:
以不同的方式遍歷聚合對象刊殉,比如向前殉摔、向后等;
對同一個聚合同時進行多個遍歷记焊;
以不同的遍歷策略來遍歷聚合逸月,比如是否需要過濾等;
多態(tài)迭代遍膜,含義是:
為不同的聚合結(jié)構(gòu)碗硬,提供統(tǒng)一的迭代接口,也就是說通過一個迭代接口可以訪問不同的聚合結(jié)構(gòu)瓢颅,這就叫做多態(tài)迭代
恩尾。上面的示例就已經(jīng)實現(xiàn)了多態(tài)迭代,事實上挽懦,標準的迭代模式實現(xiàn)基本上都是支持多態(tài)迭代的翰意;但是請注意:多態(tài)迭代可能會帶來類型安全的問題,
可以考慮使用泛型
巾兆;
- 迭代器模式的關(guān)鍵思想
聚合對象的類型很多猎物,如果對聚合對象的迭代訪問跟聚合對象本身融合在一起的話虎囚,會嚴重影響到聚合對象的可擴展性和可維護性角塑。
因此迭代器模式的關(guān)鍵思想就是把對聚合對象的遍歷和訪問從聚合對象中分離出來,放入到單獨的迭代器中淘讥,這樣聚合對象會變得簡單一些圃伶;而且迭代器和聚合對象可以獨立的變化和發(fā)展,會大大加強系統(tǒng)的靈活性
。
- 內(nèi)部迭代器和外部迭代器
所謂內(nèi)部迭代器窒朋,指的是由迭代器自己來控制迭代下一個元素的步驟搀罢,客戶端無法干預
,因此侥猩,如果想要在迭代的過程中完成工作的話榔至,客戶端就需要把操作傳給迭代器,迭代器在迭代的時候會在每個元素上執(zhí)行這個操作欺劳,類似于Java的回調(diào)機制唧取。
所謂外部迭代器,指的是由客戶端來控制迭代下一個元素的步驟
划提,像前面的示例一樣枫弟,客戶端必須顯示的調(diào)用next來迭代下一個元素。
總體來說外部迭代器比內(nèi)部迭代器要靈活一些鹏往,因此我們常見的實現(xiàn)多屬于外部迭代器淡诗,前面的例子也是實現(xiàn)的外部迭代器。
- Java中最簡單的統(tǒng)一訪問聚合的方式
如果只是想要使用一種統(tǒng)一的訪問方式來訪問聚合對象伊履,在Java中有更簡單的方式韩容,簡單到幾乎什么都不用做,利用Java 5以上版本本身的特性即可唐瀑。
但是請注意宙攻,這只是從訪問形式上一致了,但是也暴露了聚合的內(nèi)部實現(xiàn)介褥,因此并不能算是標準迭代器模式的實現(xiàn)座掘,但是從某種意義上說,可以算是隱含的實現(xiàn)了部分迭代器模式的功能柔滔。
那么怎么做呢溢陪?
為了簡單,讓我們回到?jīng)]有添加任何迭代器模式的情況下睛廊。很簡單形真,只要讓聚合對象中的結(jié)合實現(xiàn)范型即可,示例如下:
public class PayManager{
private List<PayModel> list = new ArrayList<PayModel>();
/**
* 獲取工資列表
* @return 工資列表
*/
public List<PayModel> getPayList(){
return list;
}
}
這樣一來超全,客戶端的代碼就可以改成使用增強的for循環(huán)來實現(xiàn)了咆霜,對于數(shù)組、范型的集合都可以采用一樣的方法來實現(xiàn)了嘶朱,從代碼層面上看蛾坯,就算是統(tǒng)一了訪問聚合的方式了
,修改后的客戶端代碼如下:
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);
}
}
}
3.2 使用Java的迭代器##
大家都知道脉课,在java.util包里面有一個Iterator的接口救军,在Java中實現(xiàn)迭代器模式是非常簡單的,而且java的集合框架中的聚合對象倘零,基本上都是提供了迭代器的唱遭。
下面就來把前面的例子改成用Java中的迭代器實現(xiàn),一起來看看有些什么改變呈驶。
不再需要自己實現(xiàn)的Iterator接口拷泽,直接實現(xiàn)java.util.Iterator接口就可以了,所有使用自己實現(xiàn)的Iterator接口的地方都需要修改過來袖瞻;
Java中Iterator接口跟前面自己定義的接口相比跌穗,需要實現(xiàn)的方法是不一樣的;
集合已經(jīng)提供了Iterator虏辫,那么CollectionIteratorImpl類就不需要了蚌吸,直接去掉;
好了砌庄,還是一起來看看代碼吧羹唠。
PayModel類沒有任何變化,就不示例了娄昆。
抽象的Aggregate類就是把創(chuàng)建迭代器方法返回的類型換成java中的Iterator了佩微。示例代碼如下:
import java.util.Iterator;
public abstract class Aggregate {
public abstract Iterator createIterator();
}
- 原來的ArrayIteratorImpl類,實現(xiàn)的接口改變了萌焰,實現(xiàn)代碼也需要跟著改變哺眯,示例代碼如下:
/**
* 用來實現(xiàn)訪問數(shù)組的迭代接口
*/
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() {
//暫時可以不實現(xiàn)
}
}
- 對于PayManager類扒俯,在實現(xiàn)創(chuàng)建迭代器的方法上發(fā)生了改變奶卓,不再使用自己實現(xiàn)的迭代器,改用java的集合框架實現(xiàn)的迭代器了撼玄。示例代碼如下:
public class PayManager extends Aggregate{
private List<PayModel> list = new ArrayList<PayModel>();
public List<PayModel> getPayList(){
return list;
}
public void calcPay(){
//計算工資夺姑,并把工資信息填充到工資列表里面
//為了測試,做點數(shù)據(jù)進去
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();
}
}
對于SalaryManager類掌猛,除了創(chuàng)建迭代器方法返回的類型改變外盏浙,其它都沒有改變,還是用ArrayIteratorImpl來實現(xiàn)迭代器荔茬。
接下來寫個客戶端來測試看看废膘,示例代碼如下:
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來實現(xiàn)慕蔚,比自己全部重新去做丐黄,還是要簡單一些的。
3.3 帶迭代策略的迭代器##
由于迭代器模式把聚合對象和訪問聚合的機制實現(xiàn)了分離
坊萝,所以可以在迭代器上實現(xiàn)不同的迭代策略孵稽,最為典型的就是實現(xiàn)過濾功能的迭代器
许起。
在實際開發(fā)中十偶,對于經(jīng)常被訪問的一些數(shù)據(jù)可以使用緩存菩鲜,把這些數(shù)據(jù)存放在內(nèi)存中。但是不同的業(yè)務功能需要訪問的數(shù)據(jù)是不同的惦积,還有不同的業(yè)務訪問權(quán)限能訪問的數(shù)據(jù)也是不同的接校,對于這種情況,就可以使用實現(xiàn)過濾功能的迭代器狮崩,讓不同功能使用不同的迭代器來訪問蛛勉。當然,這種情況也可以結(jié)合策略模式來實現(xiàn)睦柴。
在實現(xiàn)過濾功能的迭代器中诽凌,又有兩種常見的需要過濾的情況,一種是對數(shù)據(jù)進行整條過濾
坦敌,比如只能查看自己部門的數(shù)據(jù)侣诵;另外一種情況是對數(shù)據(jù)進行部分過濾
,比如某些人不能查看工資數(shù)據(jù)狱窘。
帶迭代策略的迭代器實現(xiàn)的一個基本思路杜顺,就是先把聚合對象的聚合數(shù)據(jù)獲取到,并存儲到迭代器里面來蘸炸,這樣迭代器就可以按照不同的策略來迭代數(shù)據(jù)了躬络。
- 帶迭代策略的迭代器示例
沿用上一個例子,來修改ArrayIteratorImpl來簡單的示意一下搭儒,不去考慮復雜的算法穷当,大致的修改有:
原來是持有聚合對象的,現(xiàn)在直接把這個聚合對象的內(nèi)容取出來存放到迭代器里面淹禾,也就是迭代的時候膘滨,直接在迭代器里面來獲取具體的聚合對象的元素,這樣才好控制迭代的數(shù)據(jù)稀拐;
在迭代器的具體實現(xiàn)上加入過濾的功能火邓;
示例代碼如下:
/**
* 用來實現(xiàn)訪問數(shù)組的迭代接口,加入了迭代策略
*/
public class ArrayIteratorImpl implements Iterator{
/**
* 用來存放被迭代的數(shù)組
*/
private PayModel[] pms = null;
/**
* 用來記錄當前迭代到的位置索引
*/
private int index = 0;
public ArrayIteratorImpl(SalaryManager aggregate){
//在這里先對聚合對象的數(shù)據(jù)進行過濾,比如工資必須在3000以下
Collection<PayModel> tempCol = new ArrayList<PayModel>();
for(PayModel pm : aggregate.getPays()){
if(pm.getPay() < 3000){
tempCol.add(pm);
}
}
//然后把符合要求的數(shù)據(jù)存放到用來迭代的數(shù)組
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++;
}
//在這里對要返回的數(shù)據(jù)進行過濾铲咨,比如不讓查看工資數(shù)據(jù)
((PayModel)retObj).setPay(0.0);
return retObj;
}
public void remove() {
//暫時可以不實現(xiàn)
}
}
- 誰定義遍歷算法的問題
在實現(xiàn)迭代器模式的時候,一個常見的問題就是:誰來定義遍歷算法蜓洪?其實帶策略的迭代器講述的也是這個問題纤勒。
在迭代器模式的實現(xiàn)中,常見有兩個地方可以來定義遍歷算法隆檀,一個就是聚合對象本身摇天,另外一個就是迭代器負責遍歷算法
粹湃。
在聚合對象本身定義遍歷的算法這種情況下,通常會在遍歷過程中泉坐,用迭代器來存儲當前迭代的狀態(tài)为鳄,這種迭代器被稱為游標,因為它僅用來指示當前的位置
腕让。比如在2.4里面示例的迭代器就屬于這種情況孤钦。
在迭代器里面定義遍歷算法,會易于在相同的聚合上使用不同的迭代算法纯丸,同時也易于在不同的聚合上重用相同的算法
偏形。比如上面帶策略的迭代器的示例,迭代器把需要迭代的數(shù)據(jù)從聚合對象中取出并存放到自己對象里面觉鼻,然后再迭代自己的數(shù)據(jù)俊扭,這樣一來,除了剛開始創(chuàng)建迭代器的時候需要訪問聚合對象外坠陈,真正迭代過程已經(jīng)跟聚合對象無關(guān)了萨惑。
當然,在迭代器里面定義遍歷算法畅姊,如果實現(xiàn)遍歷算法的時候需要訪問聚合對象的私有變量咒钟,那么將遍歷算法放入迭代器中會破壞聚合對象的封裝性
。
至于究竟使用哪一種方式若未,要具體問題具體分析朱嘴。
3.4 雙向迭代器##
所謂雙向迭代器的意思就是:可以同時向前和向后遍歷數(shù)據(jù)的迭代器。
在Java util包中的ListIterator接口就是一個雙向迭代器的示例粗合,當然自己實現(xiàn)雙向迭代器也非常容易萍嬉,只要在自己的Iterator接口中添加向前的判斷和向前獲取值的方法,然后在實現(xiàn)中實現(xiàn)即可隙疚。
延續(xù)前面2.4的示例壤追,來自己實現(xiàn)雙向迭代器,相同的部分就不去示范了供屉,只演示不同的地方行冰,先看看新的迭代器接口,示例代碼如下:
/**
* 迭代器接口伶丐,定義訪問和遍歷元素的操作悼做,實現(xiàn)雙向迭代
*/
public interface Iterator {
public void first();
public void next();
public boolean isDone();
public Object currentItem();
/**
* 判斷是否為第一個元素
* @return 如果為第一個元素,返回true哗魂,否則返回false
*/
public boolean isFirst();
/**
* 移動到聚合對象的上一個位置
*/
public void previous();
}
有了新的迭代器接口肛走,也應該有新的實現(xiàn)。示例代碼如下:
/**
* 用來實現(xiàn)訪問數(shù)組的雙向迭代接口
*/
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;
}
}
}
基本實現(xiàn)完了录别,寫個客戶端來享受一下雙向迭代的樂趣朽色。由于這個實現(xiàn)要考慮同時控制向前和向后迭代取值邻吞,而控制當前索引的是同一個值,因此在獲取向前取值得時候葫男,要先把已訪問索引減去1抱冷,然后再取值,這個跟向后取值是反過來的腾誉,注意一下
徘层。示例代碼如下:
public class Client {
public static void main(String[] args) {
//訪問新收購公司的工資列表
SalaryManager salaryManager = new SalaryManager();
//先計算再獲取
salaryManager.calcSalary();
//得到雙向迭代器
Iterator it = salaryManager.createIterator();
//首先設(shè)置迭代器到第一個元素
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();
}
//繼續(xù)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);
}
}
}
上面的示例故意先向后取值峻呕,然后再向前取值利职,這樣反復才能看出雙向迭代器的效果。運行的結(jié)果如下:
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取出來的值是一樣的呢瘦癌?
這是因為現(xiàn)在是順序迭代猪贪,當next顯示第一條的時候,內(nèi)部索引已經(jīng)指向第二條了讯私,所以這個時候再previous向前一條的時候热押,數(shù)據(jù)就是第一條數(shù)據(jù)了。
再仔細看上面的結(jié)果斤寇,發(fā)現(xiàn)這個時候繼續(xù)next數(shù)據(jù)時桶癣,數(shù)據(jù)還是第一條數(shù)據(jù),同理娘锁,剛才previous向前一條的時候牙寞,內(nèi)部索引已經(jīng)指向第一條之前了。
3.5 迭代器模式的優(yōu)缺點##
- 更好的封裝性
迭代器模式可以讓你訪問一個聚合對象的內(nèi)容莫秆,而無需暴露該聚合對象的內(nèi)部表示间雀,從而提高聚合對象的封裝性;
- 可以以不同的遍歷方式來遍歷一個聚合
使用迭代器模式镊屎,使得聚合對象的內(nèi)容和具體的迭代算法分離開惹挟,這樣就可以通過使用不同的迭代器的實例,就可以使用不同的遍歷方式來遍歷一個聚合對象了缝驳,比如上面示例的帶迭代策略的迭代器连锯。
- 迭代器簡化了聚合的接口
有了迭代器的接口,那么聚合本身就不需要再定義這些接口了用狱,從而簡化了聚合的接口定義运怖。
- 簡化客戶端調(diào)用
迭代器為遍歷不同的聚合對象提供了一個統(tǒng)一的接口,使得客戶端遍歷聚合對象的內(nèi)容變得更簡單
- 同一個聚合上可以有多個遍歷
每個迭代器保持它自己的遍歷狀態(tài)齿拂,比如前面實現(xiàn)中的迭代索引位置驳规,因此可以對同一個聚合對象同時進行多個遍歷。
3.6 思考迭代器模式##
- 迭代器模式的本質(zhì)
迭代器模式的本質(zhì):控制訪問聚合對象中的元素署海。
迭代器能實現(xiàn)“無需暴露聚合對象的內(nèi)部實現(xiàn)吗购,就能夠訪問到聚合對象中各個元素”的功能医男,看起來其本質(zhì)應該是“透明訪問聚合對象中的元素”。
但仔細思考一下捻勉,除了透明外镀梭,迭代器就沒有別的功能了嗎?很明顯還有其它的功能踱启,前面也講到了一些报账,比如“帶迭代策略的迭代器”。那么綜合來看埠偿,迭代器模式的本質(zhì)應該是“控制訪問聚合對象中的元素”透罢,而非單純的“透明”,事實上冠蒋,“透明”訪問也是“控制訪問”的一種情況
羽圃。
認識這個本質(zhì),對于識別和變形使用迭代器模式很有幫助抖剿。大家想想朽寞,現(xiàn)在的迭代模式默認的都是向前或者向后獲取一個值,也就是說都是單步迭代斩郎,那么脑融,如果想要控制一次迭代多條怎么辦呢?
這個在實際開發(fā)中是很有用的缩宜,比如在實際開發(fā)中很常用的翻頁功能的實現(xiàn)肘迎,常見的翻頁功能有如下幾種實現(xiàn)方式:
(1)純數(shù)據(jù)庫實現(xiàn):依靠SQL提供的功能實現(xiàn)翻頁,用戶每次請求翻頁的數(shù)據(jù)脓恕,就會到數(shù)據(jù)庫中獲取相應的數(shù)據(jù)膜宋;
(2)純內(nèi)存實現(xiàn):就是一次性從數(shù)據(jù)庫中把需要的所有數(shù)據(jù)都取出來放到內(nèi)存中,然后用戶請求翻頁時炼幔,從內(nèi)存中獲取相應的數(shù)據(jù)秋茫;
(3)上面兩種方式各有優(yōu)缺點:
第一種方案明顯是時間換空間的策略,每次獲取翻頁的數(shù)據(jù)都要訪問數(shù)據(jù)庫乃秀,運行速度相對比較慢肛著,而且很耗數(shù)據(jù)庫資源,但是節(jié)省內(nèi)存空間跺讯。 第二種方案是典型的空間換時間枢贿,每次是直接從內(nèi)存中獲取翻頁的數(shù)據(jù),運行速度快刀脏,但是很耗內(nèi)存局荚。
在實際開發(fā)中,小型系統(tǒng)一般采用第一種方案,基本沒有單獨采用第二種方案的耀态,因為內(nèi)存實在是太寶貴了轮傍,中大型的系統(tǒng)一般是把兩個方案結(jié)合起來,綜合利用它們的優(yōu)點首装,而又規(guī)避它們的缺點创夜,從而更好的實現(xiàn)翻頁的功能。
(4)純數(shù)據(jù)庫實現(xiàn) + 純內(nèi)存實現(xiàn)仙逻,思路是這樣的:如果每頁顯示10條記錄驰吓,根據(jù)判斷,用戶很少翻到10頁過后系奉,那好了檬贰,第一次訪問的時候,就一次性從數(shù)據(jù)庫中獲取前10頁的數(shù)據(jù)喜最,也就是100條記錄偎蘸,把這100條記錄放在內(nèi)存里面庄蹋。
這樣一來瞬内,當用戶在前10頁內(nèi)進行翻頁操作的時候,就不用再訪問數(shù)據(jù)庫了限书,而是直接從內(nèi)存中獲取數(shù)據(jù)虫蝶,這樣速度就快了。
當用戶想要獲取第11頁的數(shù)據(jù)倦西,這個時候才會再次訪問數(shù)據(jù)庫能真,對于這個時候到底獲取多少頁的數(shù)據(jù),簡單的處理就是繼續(xù)獲取10頁的數(shù)據(jù)扰柠,
比較好的方式就是根據(jù)訪問統(tǒng)計進行衰減訪問
粉铐,比如折半獲取,也就是第一次訪問數(shù)據(jù)庫獲取10頁的數(shù)據(jù)卤档,那么第二次就只獲取5頁蝙泼,如此操作直到一次從數(shù)據(jù)庫中獲取一頁的數(shù)據(jù)。這也符合正常規(guī)律劝枣,因為越到后面汤踏,被用戶翻頁到的機會也就越小了。
- 何時選用迭代器模式
建議在如下情況中舔腾,選用迭代器模式:
如果你希望提供訪問一個聚合對象的內(nèi)容溪胶,但是又不想暴露它的內(nèi)部表示的時候,可以使用迭代器模式來提供迭代器接口稳诚,從而讓客戶端只是通過迭代器的接口來訪問聚合對象哗脖,而無需關(guān)心聚合對象內(nèi)部實現(xiàn)。
如果你希望有多種遍歷方式可以訪問聚合對象,可以使用迭代器模式才避。
如果你希望為遍歷不同的聚合對象提供一個統(tǒng)一的接口丘损,可以使用迭代器模式。
3.8 相關(guān)模式##
- 迭代器模式和組合模式
這兩個模式可以組合使用工扎。
組合模式是一種遞歸的對象結(jié)構(gòu)徘钥,在枚舉某個組合對象的子對象的時候,通常會使用迭代器模式
肢娘。
- 迭代器模式和工廠方法模式
這兩個模式可以組合使用呈础。
在聚合對象創(chuàng)建迭代器的時候,通常會采用工廠方法模式來實例化相應的迭代器對象
橱健。