Java設計模式之——迭代器模式

迭代器模式簡單介紹

迭代器模式又稱爲遊標模式,是行爲型設計模式之一。迭代器模式算是一個比較古老的設計模式,其源於對容器的訪問,比如 Java 中的 List、Map、數組等,我們知道對容器對象的方法必然會涉及遍歷算法,我們可以將遍歷的算法封裝在容器中,或者不提供遍歷算法。如果我們將遍歷的方法封裝到容器中,那麼對於容器類來說就承擔了過多的功能,容器類不僅要維護自身內部的元素而且還要對外提供遍歷的接口方法,因爲遍歷狀態的存儲問題還不能對同一個容器進行多個遍歷操作,如果我們不提供遍歷方法而讓使用者自己去實現,又會讓容器的內部細節暴露無遺,正因於此,迭代器模式應運而生,在客戶訪問類與容器類之間插入了一個第三章——迭代器,很好地解決了上面所述的弊端。

迭代器的定義

提供一種方法順序訪問一個容器對象中的各個元素,而又不需要暴露該對象的內部表示。

迭代器模式的使用場景

遍歷一個容器對象時。

迭代器模式的 UML 類圖

這裏寫圖片描述

角色介紹:

  • Iterator:迭代器接口,負責定義、訪問和遍歷元素的接口;
  • ConcreteIterator:具體迭代器類,具體迭代器類的目的主要是實現迭代器接口,並記錄遍歷的當前位置。
  • Aggregate:容器接口,容器接口負責提供創建具體迭代器角色的接口
  • ConcreteAggregate:具體容器類,具體迭代器角色與容器相關聯。

根據類圖可以得出如下一個迭代器模式的通用模式代碼:

/**
 * 迭代器接口
 * @param <T>
 */
public interface Iterator<T> {

    /**
     * 是否還有下一個元素
     * @return  true表示有,false表示沒有
     */
    boolean hasNext();

    /**
     * 返回當前位置的元素並將其位置移至下一位
     * @return
     */
    T next();
}

/**
 * 具體迭代器類
 * @param <T>
 */
public class ConcreteIterator<T> implements Iterator<T> {
    private List<T> list = new ArrayList<>();
    private int cursor = 0;

    public ConcreteIterator(List<T> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return cursor != list.size();
    }

    @Override
    public T next() {
        T obj = null;
        if (this.hasNext()) {
            obj = this.list.get(cursor++);
        }
        return obj;
    }
}

/**
 * 容器接口
 * @param <T>
 */
public interface Aggregate<T> {

    /**
     * 添加一個元素
     * @param obj   元素對象
     */
    void add(T obj);

    /**
     * 移除一個元素
     * @param obj
     */
    void remove(T obj);

    /**
     * 獲取容器迭代器
     * @return  迭代器對象
     */
    Iterator<T> iterator();
}

/**
 * 具體容器類
 * @param <T>
 */
public class ConcreteAggregate<T> implements Aggregate<T> {
    private List<T> list = new ArrayList<>();

    @Override
    public void add(T obj) {
        list.add(obj);
    }

    @Override
    public void remove(T obj) {
        list.remove(obj);
    }

    @Override
    public Iterator<T> iterator() {
        return new ConcreteIterator<>(list);
    }
}

/**
 * 客戶端
 */
public class Client {
    public static void main() {
        Aggregate<String> a = new ConcreteAggregate<>();
        a.add("aKaiC");
        a.add("Android");
        a.add("IOS");
        Iterator<String> iterator = a.iterator();
        while (iterator.hasNext()) {
            Log.d("Client", iterator.next());
        }
    }
}

迭代器模式實戰

舉個例子:小明和小輝分別在公司的兩個事業部,某天老闆安排任務讓他們倆統計一下各自部門的員工數據,這很好辦嘛,建一個類用數據結構把所有員工數據存進去即可,老闆要看的時候給他用 for 循環實現,還是比較容易的,下面就先爲員工創建一個實體類:

public class Employee {
    private String name;
    private int age;
    private String sex;
    private String position;    //職位

    public Employee(String name, int age, String sex, String position) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.position = position;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", position='" + position + '\'' +
                '}';
    }
}

爲了簡化代碼,這裏就不在提供 setter 和 getter 方法了,接下來就是小明和小輝兩個部門間人員數據的構建了,對於小明的部門,小明創建一個 CompanyMing 類來存儲人員信息,這裏爲了簡化代碼,我們不提供對位添加人員信息的 add 方法,只在構造函數中添加固定數據:

public class CompanyMing {
    private List<Employee> list = new ArrayList<>();

    public CompanyMing() {
        list.add(new Employee("小民", 22, "男", "程序員"));
        list.add(new Employee("小芸", 26, "女", "測試"));
        list.add(new Employee("雪兒", 20, "女", "測試"));
        list.add(new Employee("小芳", 21, "女", "設計"));
    }

    public List<Employee> getEmployee() {
        return list;
    }
}

而對於小輝,同樣創建一個 CompanyHui 類來作爲容器:

public class CompanyHui {
    public Employee[] array = new Employee[3];

    public CompanyHui() {
        array[0] = new Employee("輝哥", 28, "男", "程序員");
        array[1] = new Employee("曉峯", 20, "男", "程序員");
        array[2] = new Employee("小輝", 22, "男", "程序員");
    }

    public Employee[] getEmployee() {
        return array;
    }
}

可見小明和小輝的內部實現是兩種方式,小明的人員信息容器的內部實質是使用的一個 List 來存儲人員信息,而小輝的實質上使用的是一個數組,如果老闆要查看人員信息就必須遍歷兩個容器:

public class Boos {
    public static void main() {
        CompanyMing ming = new CompanyMing();
        List<Employee> employee = ming.getEmployee();
        for (int i = 0; i < employee.size(); i++) {
            //打印員工信息
        }

        CompanyHui hui = new CompanyHui();
        Employee[] employee1 = hui.getEmployee();
        for (int i = 0; i < employee1.length; i++) {
            //打印員工信息
        }
    }
}

這樣的代碼沒有什麼問題,老闆也能查看到人員的信息,但是,這裏要注意的是,如果其他部門的人員信息也有各自不同的實現,那麼對於每一個部門的信息容器我們都要在 Boos 類中增加一段遍歷邏輯,這是很不科學的?這時候可以用迭代器實現,將遍歷邏輯封裝,怎麼做呢?首先我們需要等一一個迭代器接口:

/**
 * 迭代器接口
 * @param <T>
 */
public interface Iterator<T> {

    /**
     * 是否還有下一個元素
     * @return  true表示有,false表示沒有
     */
    boolean hasNext();

    /**
     * 返回當前位置的元素並將其位置移至下一位
     * @return
     */
    T next();
}

對於小明和小輝部門的人員信息容器,我們分別創建一個對應的迭代器:

public class MingIterator implements Iterator {
    private List<Employee> list;
    private int position;

    public MingIterator(List<Employee> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return position != list.size();
    }

    @Override
    public Object next() {
        return list.get(position++);
    }
}


public class HuiIterator implements Iterator {
    private Employee[] array;
    private int position;

    public HuiIterator(Employee[] array) {
        this.array = array;
    }

    @Override
    public boolean hasNext() {
        return !(position > array.length - 1 || array[position] == null);
    }

    @Override
    public Object next() {
        return array[position++];
    }
}

同時,我們定義一個容器類的接口,在該接口中定義一個能夠返回容器迭代器的方法:

public interface Company {
    Iterator iterator();
}

這時,我們修改一下兩個容器類使之實現容器接口,並返回對應的迭代器對象:

public class CompanyMing implements Company {
    private List<Employee> list = new ArrayList<>();

    public CompanyMing() {
        list.add(new Employee("小民", 22, "男", "程序員"));
        list.add(new Employee("小芸", 26, "女", "測試"));
        list.add(new Employee("雪兒", 20, "女", "測試"));
        list.add(new Employee("小芳", 21, "女", "設計"));
    }

    public List<Employee> getEmployee() {
        return list;
    }

    @Override
    public Iterator iterator() {
        return new MingIterator(list);
    }
}

    public class CompanyHui implements Company {
    public Employee[] array = new Employee[3];

    public CompanyHui() {
        array[0] = new Employee("輝哥", 28, "男", "程序員");
        array[1] = new Employee("曉峯", 20, "男", "程序員");
        array[2] = new Employee("小輝", 22, "男", "程序員");
    }

    public Employee[] getEmployee() {
        return array;
    }

    @Override
    public Iterator iterator() {
        return new HuiIterator(array);
    }
}

最後在 Boost 類中查閱人員信息將變得更簡單:

public class Boos {
    public static void main() {
        CompanyMing ming = new CompanyMing();

        CompanyHui hui = new CompanyHui();

        check(ming.iterator());
        check(hui.iterator());
    }

    private static void check(Iterator iterator) {
        while (iterator.hasNext()) {
            Log.d("打印員工信息", iterator.next().toString());
        }
    }
}

具體的輸出結果這裏給出,大家可自行嘗試。其實,在上述例子中只是做了一個假設,在所謂的自定義“容器”類中又使用了 Java 本身所提供的數據結構來存儲數據,這也算是一種不恰當的實現方式,因爲迭代器模式的規定不像其他模式那麼嚴格,實現也因人而異,好在大部分高級語言的容器類都爲我們提供了相應的迭代器而不需要開發者去手動實現。

總結

對於迭代器模式來說,其自身優點很明顯也很單一,支持以不同的方式去遍歷一個容器對象,也可以有多個遍歷,弱化了容器類與遍歷算法之間的關係,而其缺點就是對類文件的增加。

其實迭代器模式發展至今,幾乎每一種高級語言都有相應的內置實現,對於開發者而言,已經極少會去由自己來實現迭代器了,因此,這裏所介紹的內容只是爲了讓大家瞭解而非真正的去實際應用。

發佈了52 篇原創文章 · 獲贊 22 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章