概述
上篇文章,我們學(xué)習(xí)了狀態(tài)模式。狀態(tài)模式是狀態(tài)機的一種實現(xiàn)方式。它通過將事件觸發(fā)的狀態(tài)轉(zhuǎn)移和動作執(zhí)行,拆分到不同的狀態(tài)類中,以此來避免狀態(tài)機類中的分支判斷邏輯,應(yīng)對狀態(tài)機類代碼的復(fù)雜性。
本章,學(xué)習(xí)另外一種行為型設(shè)計模式,迭代器模式。它用來遍歷集合對象。不過,很多編程語言都將迭代器作為一個基礎(chǔ)的類庫,直接提供出來了。在平時的開發(fā)中,特別是業(yè)務(wù)開發(fā),直接使用即可,很少會自己去實現(xiàn)一個迭代器。不過,知其然知其所以然,弄懂原理能幫助我們更好的使用這些工具類,所以,還是有必要學(xué)習(xí)一下這個模式。
我們知道,大部分編程語言都提供了多種遍歷集合的方式,比如 for 循環(huán)、foreach 循環(huán)、迭代器等。所以,本章除了講解迭代器的原理和實現(xiàn)之外,還會重點說一下,相對于其他的遍歷方式,利用迭代器來遍歷集合的優(yōu)勢。
迭代器模式的實現(xiàn)原理
迭代器模式(Iterator Design Pattern),也叫作游標(biāo)模式(Cusor Design Pattern)。
它用來遍歷集合對象。這里說的 “集合對象” 也叫做 “容器” “聚合對象”,實際上就是包含一組對象的對象,比如數(shù)組、鏈表、樹、圖、跳表。迭代器將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責(zé)更加單一。
迭代器是用來遍歷容器的,所以,一個完整的迭代器模式一般會涉及到容器和容器迭代器兩部分內(nèi)容。為了達到基于接口而非實現(xiàn)編程的目的,容器包含容器接口、容器實現(xiàn)類,迭代器包含迭代器接口、迭代器實現(xiàn)類。對于迭代器模式,我繪制了一張簡單的類圖。
接下來通過一個例子,來講解如何實現(xiàn)一個迭代器。
概述中提到過,大部分編程語言都提供了遍歷容器的迭代器類,在平時開發(fā)中,直接拿來使用即可,幾乎不大可能從零去編寫一個迭代器。不過,這里為了講解迭代器的實現(xiàn)原理,我們假設(shè)某個新的編程語言的基礎(chǔ)類庫中,還沒有提供線性容器對應(yīng)地迭代器,需要從零開始開發(fā)。
線性數(shù)據(jù)結(jié)構(gòu)包括鏈表和數(shù)組,在大部分編程語言中都有對應(yīng)地類來封裝這兩種數(shù)據(jù)結(jié)構(gòu),在開發(fā)中直接拿來使用就可以了。假設(shè)在新的編程語言中,這兩個數(shù)據(jù)結(jié)分別對應(yīng) ArrayList
和 LinkedList
兩個類。此外,我們從兩個類中抽象出公共的接口,定義為 List
接口,以方便開發(fā)者基于接口而非實現(xiàn)編程。
現(xiàn)在,針對 ArrayList
和 LinkedList
兩個線性容器,設(shè)計實現(xiàn)對應(yīng)的迭代器。按照之前給出的迭代器類圖,先定義一個接口 Iterator
以及針對這兩種容器的迭代器實現(xiàn)類 ArrayIterator
和 LinkedIterator
。
先看下 Iterator
接口的定義。具體代碼如下所示:
// 接口定義方式一
public interface Iterator<E> {
boolean hasNext();
void next();
E currentItem();
}
// 接口定義方式二
public interface Iterator<E> {
boolean hasNext();
E next();
}
Iterator
接口有兩種定義方式。
- 在第一種定義中,
next()
函數(shù)用來將游標(biāo)后移一位元素,currentItem()
函數(shù)用來返回當(dāng)前游標(biāo)執(zhí)行的元素。 - 在第二種定義中,返回當(dāng)前元素與后移一位這兩個操作,都要放到同一個函數(shù)
next()
中完成。
第一種實現(xiàn)方式更加靈活些,比如可以多次調(diào)用 currentItem()
查詢當(dāng)前元素,而不移動游標(biāo)。所以,在接下來的實現(xiàn)中,我們選擇第一種接口定義方式。
現(xiàn)在,我們再來看一下 ArrayIterator
的代碼實現(xiàn),具體如下所示。代碼實現(xiàn)非常簡單,不需要太多解釋。
public class ArrayIterator<E> implements Iterator<E> {
private int cursor;
private ArrayList<E> arrayList;
public ArrayIterator(ArrayList<E> arrayList) {
this.cursor = 0;
this.arrayList = arrayList;
}
@Override
public boolean hasNext() {
return cursor != arrayList.size(); // 注意這里,cursor在指向最后一個元素的時候,hasNext()仍返回true
}
@Override
public void next() {
cursor++;
}
@Override
public E currentItem() {
if (cursor >= arrayList.size()) {
throw new NoSuchElementException();
}
return arrayList.get(cursor);
}
}
public class Demo {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("chen");
names.add("jian");
names.add("seng");
Iterator<String> iterator = new ArrayIterator<>(names);
while (iterator.hasNext()) {
System.out.println(iterator.currentItem());
iterator.next();
}
}
}
在上面的視線中,需要將待遍歷的容器對象,通過構(gòu)造函數(shù)傳遞給迭代器類。實際上,為了封裝迭代器的創(chuàng)建細節(jié),我們可以在容器中定義一個迭代器的 iterator()
方法,來創(chuàng)建對應(yīng)的迭代器。為了能基于接口而非實現(xiàn)編程,還需要將這個方法定義在 List
接口中。具體的代碼實現(xiàn)和使用如下所示。
public interface List<E> {
Iterator iterator();
// 省略其他接口函數(shù)...
}
public class ArrayList<E> implements List<E> {
// ...
@Override
public Iterator iterator() {
return new ArrayIterator(this);
}
// 省略其他代碼...
}
public class Demo {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("chen");
names.add("jian");
names.add("seng");
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.currentItem());
iterator.next();
}
}
}
對于 LinkedIterator
,它結(jié)構(gòu)和 ArrayIterator
完全相同,這里就不給出代碼了。
結(jié)合剛剛的例子,來總結(jié)一下迭代器設(shè)計思路。
- 迭代器需要定義
hasNext()
、currentItem()
、next()
三個最基本的方法。 - 待遍歷的容器對象通過依賴注入傳遞到迭代器類中。
- 容器通過
iterator()
方法來創(chuàng)建迭代器。
下面畫了一張類圖,你可以結(jié)合著看一看。
迭代器模式的優(yōu)勢
迭代器的原理和實現(xiàn)講完了,現(xiàn)在,一起來看一下,使用迭代器遍歷集合的優(yōu)勢。
一般來講,遍歷集合數(shù)據(jù)有三種方法:for 循環(huán)、foreach 循環(huán)、iterator 迭代器。對照這三種方式,舉例說明下:
List<String> names = new ArrayList<>();
names.add("chen");
names.add("jian");
names.add("seng");
// 第一種遍歷方式,for循環(huán)
for (int i = 0; i < names.size(); i++) {
System.out.print(names.get(i) + ",");
}
// 第二種遍歷方式,foreach循環(huán)
for (String name : names) {
System.out.print(name + ",");
}
// 第三種遍歷方式,迭代器遍歷
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next() + ","); // Java 中的迭代器接口是第二種定義方式,next()即移動游標(biāo)又返回數(shù)據(jù)
}
實際上 foreach 循環(huán)只是一個語法糖而已,底層是基于迭代器來實現(xiàn)的。也就是說,上面的代碼中的第二種遍歷方式(foreach 循環(huán))的底層實現(xiàn),就是第三種遍歷方式(迭代器遍歷)。
從上面的代碼來看,for 循環(huán)遍歷方式比起迭代器遍歷方式,代碼看起來更加簡潔。那為什么還要用迭代器來遍歷容器呢?為什么還要給容器設(shè)計對應(yīng)的迭代器呢?原因有三個。
-
首先,對于數(shù)組和鏈表這樣的數(shù)據(jù)結(jié)構(gòu),遍歷方式比較簡單,直接使用 for 循環(huán)來遍歷就足夠了。但是,對于復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(比如樹、圖),有各種復(fù)雜的遍歷方式。比如,樹有前中后序、按層遍歷,圖有深度優(yōu)先、廣度優(yōu)先遍歷等等。如果由客戶端來實現(xiàn)這些遍歷算法,勢必增加開發(fā)成本,而且容易寫錯。如果將這部分遍歷的邏輯寫到容器類中,也會導(dǎo)致容器類代碼的復(fù)雜性。
前面講過,應(yīng)對復(fù)雜性的方法就是拆分。可以將遍歷操作拆分到迭代器類中。比如,針對圖的遍歷,可以定義
DFSIterator
、BFSIterator
兩個迭代器類,讓它們分別來實現(xiàn)深度優(yōu)先和廣度優(yōu)先遍歷。 -
其次,將游標(biāo)指向的當(dāng)前位置等信息,存儲在迭代器類中,每個迭代器獨享游標(biāo)信息。這樣,我們就可以創(chuàng)建多個不同的迭代器類,同時對同一個容器進行遍歷而互不影響。
-
最后,容器和迭代器提供了抽象接口,方便在開發(fā)時,基于接口而非實現(xiàn)編程。當(dāng)需要切換新的遍歷算法時,比如,從前往后遍歷鏈表切換成從后往前遍歷鏈表,客戶端只要將迭代器類從
LinkedIterator
切換為ReversedLinkedIterator
即可,其他代碼不需要修改。此外添加新的遍歷算法,只需要擴展新的迭代器類,也更符合開閉原則。
總結(jié)
迭代器模式,也叫游標(biāo)模式。它用來遍歷集合對象。
這里說的 “集合對象”,也叫做 “容器” “聚合對象”,實際上就是包含一組對象的對象,比如數(shù)組、鏈表、樹、圖、跳表。
一個完整的迭代器模式,會設(shè)計容器和容器迭代器兩部分。為了達到基于接口而非實現(xiàn)編程的目的,容器又包含容器接口、容器實現(xiàn)類,迭代器又包含迭代器接口和迭代器實現(xiàn)類。容器中需要定義 iterator()
方法,用來創(chuàng)建迭代器。迭代器接口中需要定義 hasNext()
、next()
、currentItem()
三個最基本的方法。容器對象通過依賴注入傳遞到迭代器類中。文章來源:http://www.zghlxwxcb.cn/news/detail-851609.html
遍歷集合一般有 3 種方式:for 循環(huán)、foreach 循環(huán)、iterator 迭代器。后兩種本質(zhì)上屬于一種,都可以看做迭代器遍歷。相對于 for 循環(huán),利用迭代器遍歷有下面 3 個優(yōu)勢:文章來源地址http://www.zghlxwxcb.cn/news/detail-851609.html
- 迭代器模式封裝集合內(nèi)部的復(fù)雜數(shù)據(jù)結(jié)構(gòu),開發(fā)者不需要了解如何遍歷,直接使用容器提供的迭代器即可。
- 迭代器模式將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責(zé)更加單一。
- 迭代器模式讓添加新的遍歷算法更加容易,更符合開閉原則。此外,因為迭代器都實現(xiàn)自相同的接口,在開發(fā)中,基于接口而非實現(xiàn)編程,替換迭代器也變得更加容易。
到了這里,關(guān)于設(shè)計模式學(xué)習(xí)筆記 - 設(shè)計模式與范式 -行為型:9.迭代器模式(上):相比直接遍歷集合數(shù)據(jù),使用迭代器模式有哪些優(yōu)勢?的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!