設計之禪——迭代器模式

前言

迭代器想必大家不會陌生,作爲Java中內置的API,平時我們使用的也是非常多的。但你是否曾想過它和迭代器模式有什麼關聯?並且Java中已經有for循環遍歷,爲什麼還會需要這樣一個類?

定義

Java中大部分的數據結構都有返回iterator的方法,且都實現自Java.util.Iterator接口,這就是迭代器模式的實現和應用。那迭代器模式是什麼?有什麼用呢?

迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部的表示。

通過定義我們可以發現關鍵 “不暴露內部表示”,意思是在某些我們不願意暴露我們內部結構的場合,for循環就無法使用了,那我們就需要提供一個遍歷的工具,而其他人在使用迭代器遍歷該聚合對象的內部元素時,就不用關心其內部是用何種數據類型來存儲數據的,也就將遍歷和元素類型解耦了。那麼當有多個聚合對象,並且其內部存儲結構各不相同時,客戶端也不必再爲數據類型而糾結。說了這麼多,下面就用代碼來演示吧。

Coding

普通實現

假設現在有A、B兩家餐廳,A只售賣早餐,B則只售賣正餐,現在有個外賣平臺將會接入兩家餐廳的數據展示給客戶,但是A是用數組存儲的,B是用ArrayList存儲的。先來看看原始的實現方法:

public class A {

    private int count;
    private MenuItem[] menuItems = new MenuItem[10];

    public A() {
        add("包子", 43);
        add("饅頭", 32);
        add("豆漿", 34);
        add("牛奶", 26);
    }

    public void add(String name, double price) {
        MenuItem menuItem = new MenuItem(name, price);
        if (count >= menuItems.length) {
            System.out.println("Sorry!You can't add the new menuItem!");
        } else {
            menuItems[count++] = menuItem;
        }
    }

    public MenuItem[] getMenuItems() {
        return menuItems;
    }

}

public class B {

    private ArrayList<MenuItem> menuItems = new ArrayList<>();

    public B() {
        add("紅燒茄子", 53);
        add("青椒肉絲", 46);
        add("佛跳牆", 35);
        add("紅燒鯽魚", 46);
    }

    public void add(String name, double price) {
        menuItems.add(new MenuItem(name, price));
    }

    public ArrayList<MenuItem> getMenuItems() {
        return menuItems;
    }

}

A和B都提供了get操作用於獲取數據,

public class Takeaway {

    private A a = new A();
    private B b = new B();

    public void print() {
        MenuItem[] menuItems = a.getMenuItems();
        for (int i = 0; i < menuItems.length; i++) {
            MenuItem menuItem = menuItems[i];
            if (menuItem == null) {
                break;
            }
            System.out.println("name:" + menuItem.getName() + ", price:" + menuItem.getPrice());
        }

        ArrayList<MenuItem> tickets1 = b.getMenuItems();
        for (int i = 0; i < tickets1.size(); i++) {
            MenuItem menuItem = tickets1.get(i);
            System.out.println("name:" + menuItem.getName() + ", price:" + menuItem.getPrice());
        }
    }

}

外賣平臺在顯示菜單的時候就需要使用兩次for循環來處理,因爲他們的數據類型不一致,而且外賣平臺必須要清楚原商家是如何存儲數據的,在將來也還會有很多商家加入進來,難道都用for循環去遍歷麼,這顯然是一個糟糕的設計。

迭代器實現

首先我們需要創建一個迭代器接口,並提供公共的方法:

public interface Iterator {

    boolean hasNext();

    Object next();

}

接着爲每個商戶創建一個迭代器類,也就是將遍歷封裝,讓每個類值承擔自己的責任,也就是單一職責原則(讓類保持只有一個被改變的原因,如果將遍歷過程放到商家類內部,那麼就有了兩個使它改變的理由)

public class AIterator implements Iterator {

    private int ind;
    private MenuItem[] menuItems;

    public AIterator(MenuItem[] menuItems) {
        this.menuItems = menuItems;
    }

    @Override
    public boolean hasNext() {
        if (ind >= menuItems.length || menuItems[ind] == null) {
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        return menuItems[ind++];
    }
}

B商家的迭代器應該難不倒你,將迭代器創建好後,我們還需要對商家類進行修改,即增加一個返回迭代器的方法供外賣平臺調用:

public class A {

    private int count;
    private MenuItem[] menuItems = new MenuItem[10];

    public A() {
        add("包子", 43);
        add("饅頭", 32);
        add("豆漿", 34);
        add("牛奶", 26);
    }

    public void add(String name, double price) {
        MenuItem menuItem = new MenuItem(name, price);
        if (count >= menuItems.length) {
            System.out.println("Sorry!You can't add the new menuItem!");
        } else {
            menuItems[count++] = menuItem;
        }
    }

    public Iterator createIterator() {
        return new AIterator(menuItems);
    }
}

最後再來看看外賣平臺的變化:

public class Takeaway {

    private A a;
    private B b;

    public Takeaway(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public void print() {
        Iterator iterator = a.createIterator();
        print(iterator);

        Iterator iterator1 = b.createIterator();
        print(iterator1);
    }

    private void print(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem item = (MenuItem) iterator.next();
            System.out.println("name:" + item.getName() + ", price:" + item.getPrice());
        }
    }


}

我們可以發現第三方平臺不用在關注商家內部的具體實現了,只需要使用Iterator提供的方法即可,不過這裏需要注意的是,雖然我們將商家內部實現與第三方平臺解耦,但是如果加入新的商家,這裏的代碼又需要改變,並且多次調用print顯示菜單依然看起來不怎麼雅觀,那這個問題該如何解決呢?
出現這個問題的原因主要在於我們是針對商家的具體實現編程,而不是接口,所以我們創建一個接口,讓所有的商家實現它即可,同時也需要對外賣類做一些修改。

public class Takeaway {

    // 這裏不一定適用數組,list等也可
    private Menu[] menus;

    public Takeaway(Menu... menus) {
        this.menus = menus;
    }

    public void print() {
        for (Menu menu : menus) {
            print(menu.createIterator());
        }
    }

    private void print(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem item = (MenuItem) iterator.next();
            System.out.println("name:" + item.getName() + ", price:" + item.getPrice());
        }
    }


}

這樣不論在加入多少商家,也不管它們的數據結構是如何的,我們都不用再修改代碼了,perfect!不過上面的實現我沒有使用Java內置的API,主要是能更直觀的看到迭代器的創建過程,幫助理解,在以後使用Java的Iterator時也能更加的得心應手。

總結

迭代器是一種很簡單也很常用的模式,它利用多態的機制允許在不暴露內部結構的情況下順序地訪問聚合的元素,同時我們也從中學習到了一個設計原則——單一職責原則,在設計類時應該儘量保證類只做自己範圍職責內的事情。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章