天天看點

設計之禅——疊代器模式前言定義Coding總結

前言

疊代器想必大家不會陌生,作為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時也能更加的得心應手。

總結

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