天天看點

疊代器模式詳解

1.簡介

在現實生活以及程式設計中,經常要通路一個聚合對象中的各個元素,如“資料結構”中的連結清單周遊,通常的做法是将連結清單的建立和周遊都放在同一個類中,但這種方式不利于程式的擴充,如果要更換周遊方式就必須修改程式源代碼,這違背了“開閉原則”。

既然将周遊方法封裝在聚合類中不可取,那麼聚合類中不提供周遊方法,将周遊方法由使用者自己實作是否可行呢?答案是同樣不可取,因為這種方式會存在兩個缺點:

  1. 暴露了聚合類的内部表示,使其資料不安全;
  2. 增加客戶的負擔。

“疊代器模式”能較好地客服以上缺點,它在客戶通路類和聚合類之間插入一個疊代器,這分離了聚合對象與其周遊行為,對客戶也隐藏了其内部細節,且滿足“單一職責原則”和“開閉原則”,如Java中的collection、List、Set、Map等都包含了疊代器。

疊代器模式在生活中應用的比較廣泛,比如:物流系統中的傳送帶,不管傳送的是什麼物品,都會被打包成一個個箱子,并且有一個統一的二維碼。這樣我們不需要關心箱子裡是什麼,在分發時隻需要一個個檢查發送的目的地即可。再比如,我們平時乘坐交通工具,都是同一刷卡或者刷臉進站,而不需要關心是男性還是女性、是殘障人士還是正常人等資訊。

2.定義

疊代器(Iterator)模式的定義:提供一個對象來順序通路聚合對象中的一些列資料,而不暴露聚合對象的内部表示。

3.優點

  1. 通路一個聚合對象的内容而無須暴露它的内部表示。
  2. 周遊任務交由疊代器完成,這簡化了聚合類。
  3. 它支援以不同方式周遊一個聚合,甚至可以自定義疊代器的子類以支援新的周遊。
  4. 增加新的聚合類和疊代器類都很友善,無須修改原有代碼。
  5. 封裝性良好,為周遊不同的聚合結構提供一個統一的接口。

4.缺點

增加了類的個數,這在一定程度上增加了系統的複雜性。

5.結構

疊代器模式是通過将聚合對象的周遊行為分離出來,抽象成疊代器類來實作的,其目的是在不暴露聚合對象的内部結構的情況下,讓外部代碼透明地通路聚合的内部資料。

疊代器模式主要包含以下角色:

  1. 抽象聚合(Aggregate)角色:定義存儲、添加、删除聚合對象以及建立疊代器對象的接口。
  2. 具體聚合(ConcreteAggregate)角色:實作抽象聚合類,傳回一個具體疊代器的執行個體。
  3. 抽象疊代器(Iterator)角色:定義通路和周遊聚合元素的接口,通常包含hasNext()、first()、next()等方法。
  4. 具體疊代器(ConcreteIterator)角色:實作抽象疊代器接口中定義的方法,完成對聚合對象的周遊,記錄周遊的目前位置。

結構圖如下:

疊代器模式詳解

6.應用場景

  1. 當需要為聚合對象提供多種周遊方式時。
  2. 當需要為周遊不同的聚合結構提供一個統一的接口時。
  3. 當通路一個聚合對象的内容而無須暴露其内部細節的表示時。

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");
    }
}
           

繼續閱讀