1.簡介
在現實生活以及程式設計中,經常要通路一個聚合對象中的各個元素,如“資料結構”中的連結清單周遊,通常的做法是将連結清單的建立和周遊都放在同一個類中,但這種方式不利于程式的擴充,如果要更換周遊方式就必須修改程式源代碼,這違背了“開閉原則”。
既然将周遊方法封裝在聚合類中不可取,那麼聚合類中不提供周遊方法,将周遊方法由使用者自己實作是否可行呢?答案是同樣不可取,因為這種方式會存在兩個缺點:
- 暴露了聚合類的内部表示,使其資料不安全;
- 增加客戶的負擔。
“疊代器模式”能較好地客服以上缺點,它在客戶通路類和聚合類之間插入一個疊代器,這分離了聚合對象與其周遊行為,對客戶也隐藏了其内部細節,且滿足“單一職責原則”和“開閉原則”,如Java中的collection、List、Set、Map等都包含了疊代器。
疊代器模式在生活中應用的比較廣泛,比如:物流系統中的傳送帶,不管傳送的是什麼物品,都會被打包成一個個箱子,并且有一個統一的二維碼。這樣我們不需要關心箱子裡是什麼,在分發時隻需要一個個檢查發送的目的地即可。再比如,我們平時乘坐交通工具,都是同一刷卡或者刷臉進站,而不需要關心是男性還是女性、是殘障人士還是正常人等資訊。
2.定義
疊代器(Iterator)模式的定義:提供一個對象來順序通路聚合對象中的一些列資料,而不暴露聚合對象的内部表示。
3.優點
- 通路一個聚合對象的内容而無須暴露它的内部表示。
- 周遊任務交由疊代器完成,這簡化了聚合類。
- 它支援以不同方式周遊一個聚合,甚至可以自定義疊代器的子類以支援新的周遊。
- 增加新的聚合類和疊代器類都很友善,無須修改原有代碼。
- 封裝性良好,為周遊不同的聚合結構提供一個統一的接口。
4.缺點
增加了類的個數,這在一定程度上增加了系統的複雜性。
5.結構
疊代器模式是通過将聚合對象的周遊行為分離出來,抽象成疊代器類來實作的,其目的是在不暴露聚合對象的内部結構的情況下,讓外部代碼透明地通路聚合的内部資料。
疊代器模式主要包含以下角色:
- 抽象聚合(Aggregate)角色:定義存儲、添加、删除聚合對象以及建立疊代器對象的接口。
- 具體聚合(ConcreteAggregate)角色:實作抽象聚合類,傳回一個具體疊代器的執行個體。
- 抽象疊代器(Iterator)角色:定義通路和周遊聚合元素的接口,通常包含hasNext()、first()、next()等方法。
- 具體疊代器(ConcreteIterator)角色:實作抽象疊代器接口中定義的方法,完成對聚合對象的周遊,記錄周遊的目前位置。
結構圖如下:
6.應用場景
- 當需要為聚合對象提供多種周遊方式時。
- 當需要為周遊不同的聚合結構提供一個統一的接口時。
- 當通路一個聚合對象的内容而無須暴露其内部細節的表示時。
7.代碼樣例
1.簡單樣例
/**
* 抽象聚合
*/
interface Aggregate{
void add(Object obj);
void remove(Object obj);
Iterator getIterator();
}
/**
* 具體聚合
*/
class ConcreteAggregate implements Aggregate{
private List<Object> list = new ArrayList();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator getIterator() {
return new ConcreteIterator(list);
}
}
/**
* 抽象疊代器
*/
interface Iterator{
Object first();
Object next();
boolean hasNext();
}
/**
* 具體疊代器
*/
class ConcreteIterator implements Iterator{
private List<Object> list = null;
private int index = -1;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
@Override
public Object first() {
index = 0;
return list.get(index);
}
@Override
public Object next() {
return this.hasNext() ? list.get(++index) : null;
}
@Override
public boolean hasNext() {
return index < (list.size() - 1);
}
}
public class IteratorSimpleTest {
public static void main(String[] args){
Aggregate aggregate = new ConcreteAggregate();
aggregate.add("中山大學");
aggregate.add("華南理工");
aggregate.add("韶關學院");
System.out.println("聚合的内容有:");
Iterator it = aggregate.getIterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("first:" + it.first());
}
}
2.應用執行個體
例:用疊代器模式編寫一個浏覽婺源旅遊風景圖地程式。
分析:婺源的名勝古迹較多,要設計一個檢視相關景點圖檔和簡介的程式,用“疊代器模式”設計比較合适。
首先,設計一個婺源景點(WyViewSpot)類來儲存每張圖檔的名稱與簡介;再射界一個景點集(ViewSpotSet)接口,它是抽象聚合類,提供了增加和删除婺源景點的方法,以及擷取疊代器的方法。
然後,定義一個婺源景點集(WyViewSpotSet)類,它是具體聚合類,用ArrayList來儲存所有景點資訊,并實作父類中的抽象方法;再定義婺源景點的抽象疊代器(ViewSpotIterator)接口,其中包含了檢視景點資訊的相關方法。
最後,定義婺源景點的具體疊代器(WyViewSpotIterator)類,它實作了父類的抽象方法;用戶端程式設計成視窗程式,它初始化婺源景點集(ViewSpotSet)中的資料,并實作ActionListener接口,它通過婺源景點疊代器(ViewSpotIterator)來檢視婺源景點(WyViewSpot)的資訊。
結構圖如下:
代碼樣例:
/**
* 婺源景點類
*/
@Getter
@AllArgsConstructor
class WyViewSpot{
private String name;
private String introduce;
}
/**
* 抽象疊代器
*/
interface ViewSpotIterator{
boolean hasNext();
WyViewSpot first();
WyViewSpot next();
WyViewSpot previous();
WyViewSpot last();
}
/**
* 具體疊代器:婺源景點疊代器
*/
class WyViewSpotIterator implements ViewSpotIterator{
private ArrayList<WyViewSpot> list = null;
private int index = -1;
public WyViewSpotIterator(ArrayList<WyViewSpot> list){
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size() - 1;
}
@Override
public WyViewSpot first() {
index = 0;
return list.get(index);
}
@Override
public WyViewSpot next() {
return this.hasNext() ? list.get(++index) : null;
}
@Override
public WyViewSpot previous() {
return index > 0 ? list.get(--index) : null;
}
@Override
public WyViewSpot last() {
return list.get(list.size() - 1);
}
}
/**
* 抽象聚合:婺源景點集接口
*/
interface ViewSpotSet{
void add(WyViewSpot obj);
void remove(WyViewSpot obj);
ViewSpotIterator getIterator();
}
/**
* 具體聚合:婺源景點集
*/
class WyViewSpotSet implements ViewSpotSet{
private ArrayList<WyViewSpot> list = new ArrayList<>();
@Override
public void add(WyViewSpot obj) {
list.add(obj);
}
@Override
public void remove(WyViewSpot obj) {
list.remove(obj);
}
@Override
public ViewSpotIterator getIterator() {
return new WyViewSpotIterator(list);
}
}
/**
* 相框類
*/
class PictureFrame extends JFrame implements ActionListener{
ViewSpotSet set;
ViewSpotIterator iterator;
WyViewSpot spot;
PictureFrame(){
super("中國最美鄉村“婺源”的部分風景圖");
this.setResizable(false);
set = new WyViewSpotSet();
set.add(new WyViewSpot("江灣", "江灣景區是婺源的一個國家5A級旅遊景區,景區内有蕭江宗祠、永思街、滕家老屋、婺源人家、鄉賢園、百工坊等一大批古建築,精美絕倫,做工精細。"));
set.add(new WyViewSpot("李坑", "李坑村是一個以李姓聚居為主的古村落,是國家4A級旅遊景區,其建築風格獨特,是著名的徽派建築,給人一種安靜、祥和的感覺。"));
set.add(new WyViewSpot("思溪延村", "思溪延村位于婺源縣思口鎮境内,始建于南宋慶元五年(1199年),當時建村者俞氏以(魚)思清溪水而名。"));
set.add(new WyViewSpot("曉起村", "曉起有“中國茶文化第一村”與“國家級生态示範村”之美譽,村屋多為清代建築,風格各具特色,村中小巷均鋪青石,曲曲折折,回環如棋局。"));
set.add(new WyViewSpot("菊徑村", "菊徑村形狀為山環水繞型,小河成大半圓型,繞村莊将近一周,四周為高山環繞,符合中國的八卦“後山前水”設計,當地人稱“臉盆村”。"));
set.add(new WyViewSpot("篁嶺", "篁嶺是著名的“曬秋”文化起源地,也是一座距今近六百曆史的徽州古村;篁嶺屬典型山居村落,民居圍繞水口呈扇形梯狀錯落排布。"));
set.add(new WyViewSpot("彩虹橋", "彩虹橋是婺源頗有特色的帶頂的橋——廊橋,其不僅造型優美,而且它可在雨天裡供行人歇腳,其名取自唐詩“兩水夾明鏡,雙橋落彩虹”。"));
set.add(new WyViewSpot("卧龍谷", "卧龍谷是國家4A級旅遊區,這裡飛泉瀑流洩銀吐玉、彩池幽潭碧綠清新、山峰岩石挺拔奇巧,活脫脫一幅天然潑墨山水畫。"));
iterator = set.getIterator();
spot = iterator.first();
this.showPicture(spot.getName(), spot.getIntroduce());
}
void showPicture(String name, String introduce){
Container container = this.getContentPane();
JPanel picturePanel = new JPanel();
JPanel controlPanel = new JPanel();
String fileName = "src/iterator/picture/" + name + ".jpg";
JLabel lb = new JLabel(name, new ImageIcon(fileName), JLabel.CENTER);
JTextArea textArea = new JTextArea(introduce);
lb.setHorizontalTextPosition(JLabel.CENTER);
lb.setVerticalTextPosition(JLabel.TOP);
lb.setFont(new Font("宋體", Font.BOLD, 20));
textArea.setLineWrap(true);
textArea.setEnabled(false);
picturePanel.setLayout(new BorderLayout(5, 5));
picturePanel.add("Center", lb);
picturePanel.add("South", textArea);
JButton first, last, next, previous;
first = new JButton("第一張");
next = new JButton("下一張");
previous = new JButton("上一張");
last = new JButton("最末張");
first.addActionListener(this);
next.addActionListener(this);
previous.addActionListener(this);
last.addActionListener(this);
controlPanel.add(first);
controlPanel.add(next);
controlPanel.add(previous);
controlPanel.add(last);
container.add("Center", picturePanel);
container.add("South", controlPanel);
this.setSize(630, 550);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command.equals("第一張")){
spot = iterator.first();
}else if(command.equals("下一張")){
spot = iterator.next();
}else if(command.equals("上一張")){
spot = iterator.previous();
}else if(command.equals("最末張")){
spot = iterator.last();
}
this.showPicture(spot.getName(), spot.getIntroduce());
}
}
public class IteratorWyViewTest {
public static void main(String[] args){
new PictureFrame();
}
}
3.疊代器模式的擴充
疊代器模式長長與組合模式結合起來使用,在對組合模式中的容器構件進行通路時,經常将疊代器潛藏在組合模式的容器構成類中。當然,也可以構造一個外部疊代器來對容器構件進行通路。
結構圖如下:
代碼樣例:
interface Component{
void operation();
}
class LeafA implements Component{
@Override
public void operation() {
System.out.println("葉子節點A進行操作!");
}
}
class LeafB implements Component{
@Override
public void operation() {
System.out.println("葉子節點B進行操作!");
}
}
class Composite implements Component{
private List<Component> list = new ArrayList<>();
public void add(Component component){
list.add(component);
}
public void remove(Component component){
list.remove(component);
}
public Component getChild(int i){
return list.get(i);
}
@Override
public void operation() {
for(Component component : list){
component.operation();
}
}
public Iterator getIterator(){
return new ConcreteIterator(list);
}
}
interface Iterator{
Component first();
Component next();
boolean hasNext();
}
class ConcreteIterator implements Iterator{
private List<Component> list = null;
private int index;
public ConcreteIterator(List<Component> list){
this.list = list;
index = -1;
}
@Override
public Component first() {
index = 0;
return list.get(index);
}
@Override
public Component next() {
return hasNext() ? list.get(++index) : null;
}
@Override
public boolean hasNext() {
return index < list.size() - 1;
}
}
public class IteratorAndCompositeTest {
public static void main(String[] args){
Component leafA = new LeafA();
Component leafB = new LeafB();
Composite composite = new Composite();
composite.add(leafA);
composite.add(leafB);
composite.operation();
System.out.println("------------------");
Iterator iterator = composite.getIterator();
while(iterator.hasNext()){
iterator.next().operation();
}
}
}
4.疊代器模式在JDK源碼中的應用
Iterator是Java提供的疊代器,可以讓某個序列實作該接口來提供标準的Java疊代器。也就是說,實作Iterator接口就相當于“使用”一個疊代器。
Iterator接口源碼如下:
public interface Iterator<E> {
boolean hasNext(); // 判斷是否存在下一個對象元素
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
從上面代碼可以看出,Iterator接口定義了一些需要子類實作的方法和預設方法。其中包含了兩個主要方法,即hasNext()和next()方法。代碼中兩個default方法是JDK1.8之後才有的接口新特性。JDK1.8版本之前接口不能有方法實體。
另外,我們在學習組合模式時見過remove()方法。可以看出疊代器模式群組合模式兩者存在一定的相似性,組合模式解決的是統一樹形結構各層次通路接口,疊代器模式解決的是統一各集合對象元素周遊接口。雖然它們的适配場景不同,但核心理念是相同的。
接着來看Iterator的實作類,以ArrayList為例,ArrayList實作了List接口,List接口繼承了Collection接口,Collection接口繼承了Iterable接口,Iterable接口中包含了一個iterator方法,是以ArrayList就需要實作該iterator()方法。該方法的實作很簡單,就是傳回一個實作了Iterator接口的疊代器執行個體。
ArrayList類中的疊代器是以内部類的形式實作的,由于内部類可以直接通路外部類的成員變量,是以該疊代器内部類可以很友善地實作Iterator接口中的hasNext()和next()方法。ArrayList中的疊代器内部類名字為Itr。
源碼如下:
package java.util;
import java.util.function.Consumer;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
transient Object[] elementData;
private int size;
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
}
...
}
Itr是ArrayList中實作了Iterator接口的内部類,且在實作hasNext()和next()方法時,可以很友善地通路ArrayList的成員變量size和elementData數組。
在ArrayList内部還有幾個疊代器對Iter進行了進一步擴充,首先看ListItr。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
...
}
ListItr内部類增加了hasPrevious()方法,主要用于判斷是否還有上一個元素。另外,還有SubList對子集合的疊代處理。
5.疊代器模式在MyBatis源碼中的應用
疊代器模式在Mybatis中也是必不可少的,下面來看DefaultCursor類,源碼如下:
public class DefaultCursor<T> implements Cursor<T> {
...
private final CursorIterator cursorIterator = new CursorIterator();
...
}
DefaultCursor實作了Cursor接口,且定義了一個成員變量cursorIterator,其類型為CursorIterator。
繼續檢視CursorIterator類的源碼實作,它是DefaultCursor的一個内部類,實作了JDK中的Iterator接口,源碼如下:
private class CursorIterator implements Iterator<T> {
T object;
int iteratorIndex;
private CursorIterator() {
this.iteratorIndex = -1;
}
public boolean hasNext() {
if (this.object == null) {
this.object = DefaultCursor.this.fetchNextUsingRowBound();
}
return this.object != null;
}
public T next() {
T next = this.object;
if (next == null) {
next = DefaultCursor.this.fetchNextUsingRowBound();
}
if (next != null) {
this.object = null;
++this.iteratorIndex;
return next;
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove element from Cursor");
}
}