目錄
本文的結(jié)構(gòu)如下:
- 引言
- 什么是迭代器模式
- 模式的結(jié)構(gòu)
- 典型代碼
- 代碼示例
- 優(yōu)點和缺點
- 適用環(huán)境
- 模式應(yīng)用
一、引言
在平時生活中,可能有這樣的場景,一天的高強度敲代碼特別疲累,下班后又在十字路口堵了大半天,好不容易回到家中,啥也不想干,就往沙發(fā)上一躺,拿起遙控器,打開電視,選了一個愛看的頻道,哇,全是美女,好吧,可惜太累了,居然睡著了。
這里的電視就是一個存放頻道的容器,而遙控器則方便我們?nèi)ピL問這個電視里的頻道,并且壓根不知道電視到底是怎么存放頻道的,也不需要知道。
在軟件開發(fā)中,也存在像電視這樣的類,它用來存儲多個成員對象,這些類稱為聚合類(Aggregate Classes),對應(yīng)的對象稱為聚合對象。為了更加方便地操作這些聚合對象,同時可以很靈活地為聚合對象增加不同的遍歷方法,可以為這些類提供一個遙控器樣的角色,可以訪問一個聚合對象中的元素但又不需要暴露它的內(nèi)部結(jié)構(gòu),稱為迭代器。
這種設(shè)計其實就是迭代器模式。
二、什么是迭代器模式
聚合對象用來存儲一系列數(shù)據(jù)。其主要有兩個職責(zé):一是存儲數(shù)據(jù);二是遍歷數(shù)據(jù)。從依賴性來看,前者是聚合對象的基本職責(zé);而后者既是可變化的,又是可分離的。這時,將遍歷數(shù)據(jù)的行為從聚合對象中分離出來,封裝在一個被稱之為“迭代器”的對象中,由迭代器來提供遍歷聚合對象內(nèi)部數(shù)據(jù)的行為,這將簡化聚合對象的設(shè)計,更符合“單一職責(zé)原則”的要求。
迭代器模式定義如下:
迭代器模式(Iterator Pattern):提供一種方法來訪問聚合對象,而不用暴露這個對象的內(nèi)部表示,其別名為游標(biāo)(Cursor)。迭代器模式是一種對象行為型模式。
迭代器模式將為聚合對象提供一個遙控器,通過引入迭代器,客戶端無須了解聚合對象的內(nèi)部結(jié)構(gòu)即可實現(xiàn)對聚合對象中成員的遍歷,還可以根據(jù)需要很方便地增加新的遍歷方式。
三、模式的結(jié)構(gòu)
迭代器模式的UML類圖如下:
在迭代器模式中包含的角色有:
Aggregate(抽象聚合類):它用于存儲和管理元素對象,聲明一個createIterator()方法用于創(chuàng)建一個迭代器對象,充當(dāng)抽象迭代器工廠角色。
ConcreteAggregate(具體聚合類):它實現(xiàn)了在抽象聚合類中聲明的createIterator()方法,該方法返回一個與該具體聚合類對應(yīng)的具體迭代器ConcreteIterator實例。
Iterator(抽象迭代器):它定義了訪問和遍歷元素的接口,聲明了用于遍歷數(shù)據(jù)元素的方法,例如:用于判斷是否還有下一個元素的hasNext()方法,用于獲取獲取下一個元素的next()方法,在具體迭代器中將實現(xiàn)這些方法。
ConcreteIterator(具體迭代器):它依賴具體的聚合對象,實現(xiàn)了抽象迭代器接口,完成對聚合對象的遍歷,同時在具體迭代器中通過游標(biāo)來記錄在聚合對象中所處的當(dāng)前位置,在具體實現(xiàn)時,游標(biāo)通常是一個表示位置的非負(fù)整數(shù)。
在迭代器模式中,提供了一個外部的迭代器來對聚合對象進(jìn)行訪問和遍歷,迭代器定義了一個訪問該聚合元素的接口,并且可以跟蹤當(dāng)前遍歷的元素,了解哪些元素已經(jīng)遍歷過而哪些沒有。迭代器的引入,將使得對一個復(fù)雜聚合對象的操作變得簡單。
四、典型代碼
抽象聚合類的典型代碼如下(這里是抽象類,也可以是接口):
public abstract class Aggregate<T> {
abstract Iterator<T> createIterator();
}
具體聚合類的典型代碼如下:
public class ConcreteAggregate<T> extends Aggregate<T> {
public ConcreteAggregate(List<T> objects) {
super(objects);
}
Iterator<T> createIterator() {
return new ConcreteIterator<T>(this);
}
}
抽象迭代器的典型代碼如下:
public interface Iterator<T> {
boolean hasNext();//判斷是否存在下一個元素
T next();//返回下個元素
}
具體迭代器的典型代碼如下:
public class ConcreteIterator<T> implements Iterator<T> {
private ConcreteAggregate<T> concreteAggregate;//維持一個對具體聚合對象的引用,以便于訪問存儲在聚合對象中的數(shù)據(jù)
private int cursor; //定義一個游標(biāo),用于記錄當(dāng)前訪問位置
public ConcreteIterator(ConcreteAggregate<T> concreteAggregate){
this.concreteAggregate = concreteAggregate;
}
public boolean hasNext() {
return cursor != concreteAggregate.getObjects().size();
}
public T next() {
//toDo
return null;
}
}
五、代碼示例
假設(shè)某個公司需要開發(fā)一個設(shè)備管理系統(tǒng),該系統(tǒng)需要對用戶數(shù)據(jù),設(shè)備信息進(jìn)行遍歷,要實現(xiàn)這個功能,怎么做呢?
5.1、繼承復(fù)用
先看下最常規(guī)的做法。
為了復(fù)用遍歷的功能,一般會建立一個抽象的數(shù)據(jù)集合類AbstractObjectList,里面實現(xiàn)一些常用的方法用于訪問,然后存放用戶數(shù)據(jù)和設(shè)備數(shù)據(jù)的類都從AbstractObjectList繼承,則可以復(fù)用這些方法。
如上圖所以,也許還有更多的方法,這里只列舉幾個。
但是這樣設(shè)計后,會發(fā)現(xiàn)有幾個缺點:
- addObject()、removeObject()等方法用于管理數(shù)據(jù),而next()、isLast()、previous()、isFirst()等方法用于遍歷數(shù)據(jù)。這將導(dǎo)致聚合類的職責(zé)過重,它既負(fù)責(zé)存儲和管理數(shù)據(jù),又負(fù)責(zé)遍歷數(shù)據(jù),違反了“單一職責(zé)原則”,由于聚合類非常龐大,實現(xiàn)代碼過長,還將給測試和維護(hù)增加難度。
- 如果將抽象聚合類聲明為一個接口,則在這個接口中充斥著大量方法,不利于子類實現(xiàn),違反了“接口隔離原則”。
- 如果將所有的遍歷操作都交給子類來實現(xiàn),將導(dǎo)致子類代碼龐大,而且必須暴露AbstractObjectList的內(nèi)部存儲細(xì)節(jié),向子類公開自己的私有屬性,否則子類無法實施對數(shù)據(jù)的遍歷,這將破壞AbstractObjectList類的封裝性。
5.2、迭代器模式
迭代器模式則可以規(guī)避繼承的缺點,它將聚合類中負(fù)責(zé)遍歷數(shù)據(jù)的方法提取出來,封裝到專門的類中,實現(xiàn)數(shù)據(jù)存儲和數(shù)據(jù)遍歷分離,無須暴露聚合類的內(nèi)部屬性即可對其進(jìn)行操作。
抽象聚合類:
public abstract class AbstractObjectList<T> {
private List<T> objects;
public AbstractObjectList(List<T> objects){
this.objects = objects;
}
public void addObject(T e){
this.objects.add(e);
}
public void removeObject(T e){
this.objects.remove(e);
}
public List<T> getObjects(){
return objects;
}
abstract AbstractIterator<T> createIterator();
}
具體聚合類:
public class UserList extends AbstractObjectList<User> {
public UserList(List<User> users) {
super(users);
}
AbstractIterator<User> createIterator() {
return new UserIterator(this);
}
}
抽象迭代器:
public interface AbstractIterator<T> {
public void next(); //移至下一個元素
public boolean isLast(); //判斷是否為最后一個元素
public void previous(); //移至上一個元素
public boolean isFirst(); //判斷是否為第一個元素
public T getNextItem(); //獲取下一個元素
public T getPreviousItem(); //獲取上一個元素
}
用戶數(shù)據(jù)迭代類:
public class UserIterator implements AbstractIterator<User> {
private UserList userList;
private List<User> users;
private int cursor1; //定義一個游標(biāo),用于記錄正向遍歷的位置
private int cursor2; //定義一個游標(biāo),用于記錄逆向遍歷的位置
public UserIterator(UserList userList) {
this.userList = userList;
this.users = userList.getObjects();//獲取集合對象
cursor1 = 0; //設(shè)置正向遍歷游標(biāo)的初始值
cursor2 = users.size() -1; //設(shè)置逆向遍歷游標(biāo)的初始值
}
public void next() {
if(cursor1 < users.size()) {
cursor1++;
}
}
public boolean isLast() {
return (cursor1 == users.size());
}
public void previous() {
if (cursor2 > -1) {
cursor2--;
}
}
public boolean isFirst() {
return (cursor2 == -1);
}
public User getNextItem() {
return users.get(cursor1);
}
public User getPreviousItem() {
return users.get(cursor2);
}
}
測試:
public class Client {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
users.add(new User("孫悟空", "男娃"));
users.add(new User("貂蟬", "女娃"));
users.add(new User("關(guān)二爺", "男娃"));
users.add(new User("紫霞仙子", "女娃"));
users.add(new User("勒布朗", "男娃"));
users.add(new User("胡歌", "男娃"));
AbstractObjectList<User> list;//創(chuàng)建聚合對象
AbstractIterator<User> iterator;//創(chuàng)建迭代器對象
list = new UserList(users);
iterator = list.createIterator();
System.out.println("正向遍歷:");
while (!iterator.isLast()){
System.out.println(iterator.getNextItem() + ",");
iterator.next();
}
System.out.println();
System.out.println("-----------------------------");
System.out.println("逆向遍歷:");
while (!iterator.isFirst()){
System.out.println(iterator.getPreviousItem() + ",");
iterator.previous();
}
}
}
如果需要增加一個新的具體聚合類,如設(shè)備數(shù)據(jù)集合類,并且需要為設(shè)備數(shù)據(jù)集合類提供不同于用戶數(shù)據(jù)集合類的正向遍歷和逆向遍歷操作,只需增加一個新的聚合子類和一個新的具體迭代器類即可,原有類庫代碼無須修改,符合“開閉原則”;
如果需要為用戶聚合類更換一個迭代器,只需要增加一個新的具體迭代器類作為抽象迭代器類的子類,重新實現(xiàn)遍歷方法,原有迭代器代碼無須修改,也符合“開閉原則”;
但是如果要在迭代器中增加新的方法,則需要修改抽象迭代器源代碼,這將違背“開閉原則”。
5.3、使用內(nèi)部類實現(xiàn)迭代器
在迭代器模式結(jié)構(gòu)圖中,具體迭代器類和具體聚合類之間存在雙重關(guān)系,其中一個關(guān)系為關(guān)聯(lián)關(guān)系,在具體迭代器中需要維持一個對具體聚合對象的引用,該關(guān)聯(lián)關(guān)系的目的是訪問存儲在聚合對象中的數(shù)據(jù),以便迭代器能夠?qū)@些數(shù)據(jù)進(jìn)行遍歷操作。
除了使用關(guān)聯(lián)關(guān)系外,為了能夠讓迭代器可以訪問到聚合對象中的數(shù)據(jù),我們還可以將迭代器類設(shè)計為聚合類的內(nèi)部類。
public class UserList extends AbstractObjectList<User> {
public UserList(List<User> users) {
super(users);
}
AbstractIterator<User> createIterator() {
return new UserIterator(this);
}
private class Itr implements AbstractIterator<User> {
private int cursor1;
private int cursor2;
public Itr(){
cursor1 = 0;
cursor2 = getObjects().size() -1;
}
public void next() {
if(cursor1 < getObjects().size()) {
cursor1++;
}
}
public boolean isLast() {
return (cursor1 == getObjects().size());
}
public void previous() {
if(cursor2 > -1) {
cursor2--;
}
}
public boolean isFirst() {
return (cursor2 == -1);
}
public User getNextItem() {
return getObjects().get(cursor1);
}
public User getPreviousItem() {
return getObjects().get(cursor2);
}
}
}
用不用內(nèi)部類實現(xiàn),對客戶端來說都是一樣的,客戶端無須關(guān)心具體迭代器對象的創(chuàng)建細(xì)節(jié),只需通過調(diào)用工廠方法createIterator()即可得到一個可用的迭代器對象。
說到底,其實迭代器模式就是讓聚合類的管理數(shù)據(jù)的責(zé)任和遍歷的責(zé)任分離。
六、優(yōu)點和缺點
6.1、優(yōu)點
迭代器模式的主要優(yōu)點如下:
- 它支持以不同的方式遍歷一個聚合對象,在同一個聚合對象上可以定義多種遍歷方式(比如JDK ArrayList中,有Iterator和ListIterator)。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷算法,我們也可以自己定義迭代器的子類以支持新的遍歷方式。
- 迭代器簡化了聚合類。由于引入了迭代器,在原有的聚合對象中不需要再自行提供數(shù)據(jù)遍歷等方法,這樣可以簡化聚合類的設(shè)計,符合“類的職責(zé)單一原則”。
- 在迭代器模式中,由于引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足“開閉原則”的要求。
6.2、缺點
迭代器模式的主要缺點如下:
- 由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對應(yīng)增加新的迭代器類,類的個數(shù)成對增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
- 抽象迭代器的設(shè)計難度較大,需要充分考慮到系統(tǒng)將來的擴(kuò)展,例如JDK內(nèi)置迭代器Iterator就無法實現(xiàn)逆向遍歷,如果需要實現(xiàn)逆向遍歷,只能通過其子類ListIterator等來實現(xiàn),而ListIterator迭代器無法用于操作Set類型的聚合對象。在自定義迭代器時,創(chuàng)建一個考慮全面的抽象迭代器并不是件很容易的事情。
七、適用場景
在以下情況下可以考慮使用迭代器模式:
- 訪問一個聚合對象的內(nèi)容而無須暴露它的內(nèi)部表示。將聚合對象的訪問與內(nèi)部數(shù)據(jù)的存儲分離,使得訪問聚合對象時無須了解其內(nèi)部實現(xiàn)細(xì)節(jié)。
- 需要為一個聚合對象提供多種遍歷方式。
- 為遍歷不同的聚合結(jié)構(gòu)提供一個統(tǒng)一的接口,在該接口的實現(xiàn)類中為不同的聚合結(jié)構(gòu)提供不同的遍歷方式,而客戶端可以一致性地操作該接口。
迭代器模式是一種使用頻率非常高的設(shè)計模式,通過引入迭代器可以將數(shù)據(jù)的遍歷功能從聚合對象中分離出來,聚合對象只負(fù)責(zé)存儲數(shù)據(jù),而遍歷數(shù)據(jù) 由迭代器來完成。由于很多編程語言的類庫都已經(jīng)實現(xiàn)了迭代器模式,因此在實際開發(fā)中,我們只需要直接使用Java、C#等語言已定義好的迭代器即可,迭代器已經(jīng)成為我們操作聚合對象的基本工具之一。
八、模式應(yīng)用
JDK內(nèi)置的迭代器就是一個很好的例子。
在Java集合框架中,常用的List和Set等聚合類都繼承(或?qū)崿F(xiàn))了java.util.Collection接口,在Collection接口中聲明了如下方法(部分):
里面有一個iterator方法,返回一個迭代器,用于數(shù)據(jù)的遍歷。
Iterator接口有幾個方法:
JDK中的模式很復(fù)雜,從idea中掏出List的部分類圖看看:
Collection接口和Iterator接口充當(dāng)了迭代器模式的抽象層,分別對應(yīng)于抽象聚合類和抽象迭代器,而Collection接口的子類充當(dāng)了具體聚合類,Iterator的子類則充當(dāng)了具體迭代器。