設(shè)計(jì)模式系列教程—Iterator?Pattern(迭代器模式)

10 Iterator Pattern(迭代器模式)

前言:幫助客戶用同樣的方法遍歷不同的集合。
需求:
由于海岸城的店租極具增長,Vander的Pizza店和燒烤店需要合并在一起了,合并在一起,這兩家店的主廚A和主廚B,他們的菜單實(shí)現(xiàn)卻不相同,Pizza店的菜單是使用List,而燒烤店的菜單用的卻是數(shù)組,讓我們先來看看他們菜單的實(shí)現(xiàn)。
首先兩個(gè)主廚都是使用了MenuItem來寫自己的菜單的。
MenuItem:

public class MenuItem {

    private String name;
    
    private String desc;
    
    private double price;

    public MenuItem(String name, String desc, double price) {
        super();
        this.name = name;
        this.desc = desc;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
    
}

PizzaMenu:

public class PizzaMenu {

    private ArrayList<MenuItem> menuItem;
    
    public PizzaMenu() {
        menuItem = new ArrayList<MenuItem>();
        menuItem.add(new MenuItem("FruitPizza", "Hawaii Style", 38.0));
        menuItem.add(new MenuItem("BuffPizza", "American Style", 28.0));
        menuItem.add(new MenuItem("TunaPizza", "Japan Style", 18.0));
    }
    
    public void addItem(MenuItem iterm) {
        menuItem.add(iterm);
    }

    public ArrayList<MenuItem> getMenuItem() {
        return menuItem;
    }

    public void setMenuItem(ArrayList<MenuItem> menuItem) {
        this.menuItem = menuItem;
    }
    
}

BarbecueMenu:

public class BarbecueMenu {

    private static final int MAX_ITEMS = 5;
    
    private int numberOfIterms = 0;
    
    private MenuItem[] menuItems;
    
    public BarbecueMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem(new MenuItem("chicken", "with pepper", 10));
        addItem(new MenuItem("tofu", "with pepper", 5));
        addItem(new MenuItem("fragrant-flowered garlic", "with pepper", 10));
    }
    
    public void addItem(MenuItem menuItem) {
        if(numberOfIterms >= MAX_ITEMS) {
            System.err.println("sorry, menu is full!");
        } else {
            menuItems[numberOfIterms] = menuItem;
            numberOfIterms = numberOfIterms + 1;
        }
    }
    
}

從代碼可以看出,PizzaMenu是用List結(jié)構(gòu)來存放菜單項(xiàng)目的,而BarbecueMenu則是用數(shù)組來存放菜單項(xiàng)目的。Vander作為老板又是他大顯神威的時(shí)候了,他需要做一個(gè)菜單綜合顯示平臺(tái),顯示出PizzaMenu和BarbecueMenu。我們來看看Vander的實(shí)現(xiàn):

public class MenuAdmin {

    private BarbecueMenu barbecueMenu;

    private PizzaMenu pizzaMenu;

    public MenuAdmin(BarbecueMenu barbecueMenu, PizzaMenu pizzaMenu) {
        super();
        this.barbecueMenu = barbecueMenu;
        this.pizzaMenu = pizzaMenu;
    }

    public void displayMenu() {
        MenuItem[] barbecueItems = barbecueMenu.getMenuItems();
        ArrayList<MenuItem> pizzaItems = pizzaMenu.getMenuItem();
        for (int i = 0; i < barbecueItems.length; i++) {//數(shù)組的大小為5
            if(barbecueItems[i] != null) {
                System.out.println("name:" + barbecueItems[i].getName() + "-desc:" + barbecueItems[i].getDesc()
                        + "-price:" + barbecueItems[i].getPrice());
            }
        }
        
        for(MenuItem item : pizzaItems) {
            System.out.println("name:" + item.getName() + "-desc:" + item.getDesc()
                    + "-price:" + item.getPrice());
        }

    }

}

實(shí)現(xiàn)效果:

image.png

Vander這么設(shè)計(jì)真的好嗎?
1、MenuAdmin是不是直接針對(duì)PizzaMenu跟BarbecueMenu進(jìn)行編碼的,而不是針對(duì)接口編程。
2、如果又加入了日式料理菜單,而菜單是用HashMap實(shí)現(xiàn)的,豈不是又得需要修改很多MenuAdmin的代碼。
3、MenuAdmin直接關(guān)心了PizzaMenu和BarbecueMenu的內(nèi)部實(shí)現(xiàn)了,違反了封裝原則。
4、displayMenu部分有重復(fù)代碼,有兩個(gè)類似的循環(huán),如果加入第三個(gè)菜單又需要多一個(gè)循環(huán)。
Panda大師一看,又是糟糕的設(shè)計(jì),Vander你就不能找一個(gè)迭代器來遍歷這些數(shù)據(jù)嗎,不同的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)不同的迭代器,MenuAdmin只需要用同樣的循環(huán)就可以遍歷這些菜單項(xiàng)了。接著Panda開始寫迭代器(Iterator接口)。

Iterator(迭代器)接口:

public interface Iterator<T> {

    /**
     * 判斷集合是否有下一項(xiàng)
     * @return
     */
    boolean hasNext();
    
    /**
     * 獲取集合的下一項(xiàng)
     * @return
     */
    T next();
    
}

BarbecueMenuIterator:

public class BarbecueMenuIterator implements Iterator<MenuItem> {

    private MenuItem[] menuItems;
    
    private int position = 0;
    
    public BarbecueMenuIterator(MenuItem[] menuItems) {
        super();
        this.menuItems = menuItems;
    }

    public boolean hasNext() {
        if(position >= 0 && menuItems[position] != null) {
            return true;
        }
        return false;
    }

    public MenuItem next() {
        if(position >= 0 && menuItems[position] != null) {
            MenuItem item = menuItems[position];  
            position++;
            return item;
        }
        return null;
    }

}

PizzaMenuIterator:

public class PizzaMenuIterator implements Iterator<MenuItem> {

    private ArrayList<MenuItem> menuItems;
    
    private int position = 0;
    
    public PizzaMenuIterator(ArrayList<MenuItem> menuItems) {
        super();
        this.menuItems = menuItems;
    }

    public boolean hasNext() {
        if(position < menuItems.size() && menuItems.get(position) != null) {
            return true;
        }
        return false;
    }

    public MenuItem next() {
        if(position < menuItems.size() && menuItems.get(position) != null) {
            MenuItem item = menuItems.get(position);  
            position++;
            return item;
        }
        return null;
    }

}

BarbecueMenu:

public class BarbecueMenu {

    private static final int MAX_ITEMS = 5;
    
    private int numberOfIterms = 0;
    
    private MenuItem[] menuItems;
    
    public BarbecueMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem(new MenuItem("chicken", "with pepper", 10));
        addItem(new MenuItem("tofu", "with pepper", 5));
        addItem(new MenuItem("fragrant-flowered garlic", "with pepper", 10));
    }
    
    public void addItem(MenuItem menuItem) {
        if(numberOfIterms >= MAX_ITEMS) {
            System.err.println("sorry, menu is full!");
        } else {
            menuItems[numberOfIterms] = menuItem;
            numberOfIterms = numberOfIterms + 1;
        }
    }

    public Iterator<MenuItem> creatIterator() {
        BarbecueMenuIterator barbecueMenuIterator = new BarbecueMenuIterator(menuItems);
        return barbecueMenuIterator;
    }

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

MenuAdmin:

public class MenuAdmin {

    private BarbecueMenu barbecueMenu;

    private PizzaMenu pizzaMenu;

    public MenuAdmin(BarbecueMenu barbecueMenu, PizzaMenu pizzaMenu) {
        super();
        this.barbecueMenu = barbecueMenu;
        this.pizzaMenu = pizzaMenu;
    }
    
    public void displayMenus() {
        traverseMenus(barbecueMenu.creatIterator());
        traverseMenus(pizzaMenu.createIterator());
    }
    
    public void traverseMenus(Iterator<MenuItem> iterator) {
        while(iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                    + "-price:" + menuItem.getPrice());
        }
    }
    
}

下面是迭代器實(shí)現(xiàn)的基礎(chǔ)類圖:


這里可能會(huì)有幾個(gè)問題:
1、能不能直接用BarbecueMenu直接實(shí)現(xiàn)迭代器接口?
答案是可以的,但是代碼侵入性太大,我們要保證原有類不進(jìn)行太大的變動(dòng),再說了廚師們也不喜歡你亂搞。
2、MenuAdmin中的兩個(gè)屬性能不能直接是兩個(gè)菜單對(duì)應(yīng)的迭代器?
答案也是可以的,但是這樣雖然實(shí)現(xiàn)上沒有問題,但是迭代器作為較為低層的部分,這么做在邏輯上說不過去,本來菜單管理的類里面就應(yīng)該是菜單管理的,突然屬性變成了迭代器,這就有點(diǎn)奇怪了。

分析:
實(shí)際上當(dāng)前的MenuAdmin依然是針對(duì)實(shí)現(xiàn)編程的,PizzaMenu和BarbecueMenu都有createIterator方法,所以可以定義一個(gè)Menu接口,java.util中的ArrayList實(shí)際上已經(jīng)幫我們實(shí)現(xiàn)了Iterator了,我們直接用就可以了。接下來,進(jìn)行下一步改造。
我們首先刪除我們自己定義的Iterator接口和PizzaMenuIterator(由于ArrayList本身就有迭代器實(shí)現(xiàn)),然后import進(jìn)java.util.Iterator接口,然后將MenuAdmin中的實(shí)現(xiàn)改成接口,改寫PizzaMenu類中的createIterator方法就完成了。
Menu:

public interface Menu<T> {

    /**
     * 創(chuàng)建迭代器
     * @return
     */
    Iterator<T> createIterator();
    
}

PizzaMenu:

public class PizzaMenu implements Menu<MenuItem> {

    private ArrayList<MenuItem> menuItems;
    
    public PizzaMenu() {
        menuItems = new ArrayList<MenuItem>();
        menuItems.add(new MenuItem("FruitPizza", "Hawaii Style", 38.0));
        menuItems.add(new MenuItem("BuffPizza", "American Style", 28.0));
        menuItems.add(new MenuItem("TunaPizza", "Japan Style", 18.0));
    }
    
    public void addItem(MenuItem menuIterm) {
        menuItems.add(menuIterm);
    }

    public Iterator<MenuItem> createIterator() {
        Iterator<MenuItem> menuItemIterator =  menuItems.iterator();
        return menuItemIterator;
    }

    public void setMenuItem(ArrayList<MenuItem> menuItems) {
        this.menuItems = menuItems;
    }
    
}

MenuAdmin:

public class MenuAdmin {

    private Menu<MenuItem> barbecueMenu;

    private Menu<MenuItem> pizzaMenu;

    public MenuAdmin(Menu<MenuItem> barbecueMenu, Menu<MenuItem> pizzaMenu) {
        super();
        this.barbecueMenu = barbecueMenu;
        this.pizzaMenu = pizzaMenu;
    }
    
    public void displayMenus() {
        traverseMenus(barbecueMenu.createIterator());
        traverseMenus(pizzaMenu.createIterator());
    }
    
    public void traverseMenus(Iterator<MenuItem> iterator) {
        while(iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                    + "-price:" + menuItem.getPrice());
        }
    }   

}

分析:
這么寫之后有什么好處呢:
1、MenuAdmin不再針對(duì)于實(shí)現(xiàn)編程,而是針對(duì)接口(Menu)編程,減少了其對(duì)具體類的依賴。
2、每個(gè)菜單都要求要實(shí)現(xiàn)Menu接口,所以每個(gè)菜單必須提供createIterator方法,讓菜單管理類能夠獲得Iterator。MenuAdmin類也不需要關(guān)心每個(gè)菜單內(nèi)部用了什么結(jié)構(gòu)去存儲(chǔ)數(shù)據(jù),只需要用迭代器的方法就能完成遍歷。

下面我們隆重有請(qǐng)迭代器模式登場(chǎng):

迭代器模式:提供一種方法順序訪問一個(gè)聚合對(duì)象中的各個(gè)元素,而又不暴露其內(nèi)部的實(shí)現(xiàn)。迭代器模式把元素之間的游走的責(zé)任交給迭代器,而不是聚合對(duì)象,這不僅讓聚合的接口和實(shí)現(xiàn)變得更簡(jiǎn)潔,也可以讓聚合更專注在它所應(yīng)該專注的事情(也就是管理對(duì)象集合),而不必去理會(huì)遍歷的事情。

迭代器的套路如下圖所示,這里的Client對(duì)應(yīng)MenuAdmin,ConcreteInterator對(duì)應(yīng)PizzaMenuIterator和BarbecueMenuIterator,Aggregate對(duì)應(yīng)Menu接口,ConcreteAggregate對(duì)應(yīng)PizzaMenu和BarbecueMenu。

迭代器一般類圖

這里簡(jiǎn)單說明一下聚合對(duì)象,首先當(dāng)我們說集合的時(shí)候,是指一群對(duì)象,其存儲(chǔ)方式可以是各種各樣的數(shù)據(jù)結(jié)構(gòu),例如列表、數(shù)組、散列表,有時(shí)候集合也會(huì)叫做聚合。

還記得我們剛剛討論的嗎,為什么不直接用PizzaMenu來實(shí)現(xiàn)Iterator接口然后實(shí)現(xiàn)遍歷集合的這些方法?
當(dāng)你這么做之后,就會(huì)發(fā)現(xiàn)PizzaMenu不僅僅要維護(hù)MenuItems這個(gè)集合,還要承擔(dān)遍歷這個(gè)集合的責(zé)任,這樣的話,這個(gè)集合改變,MenuItem就必須改變,若遍歷的方式改變,這個(gè)集合又要改變,這樣它就承擔(dān)了兩種變化的風(fēng)險(xiǎn)了。根據(jù)這個(gè)理念,我們引入了單一變化原則來說明這件事情。
單一變化原則:\color{blue}{一個(gè)類應(yīng)該只有一個(gè)引起變化的原因。}
類的每一個(gè)責(zé)任都有改變的潛在區(qū)域。超過一個(gè)責(zé)任,意味著超過一個(gè)改變的區(qū)域。這個(gè)原則告訴我們盡量讓每個(gè)類保持“單一責(zé)任”。
\color{blue}{高內(nèi)聚低耦合}是我們經(jīng)常聽到的,\color{blue}{內(nèi)聚}是用來度量一個(gè)類或一個(gè)模塊緊密地達(dá)到單一目的或責(zé)任。當(dāng)一個(gè)類或模塊被設(shè)計(jì)成只支持一組相關(guān)地功能時(shí),我們說它具有高內(nèi)聚;反之,當(dāng)被設(shè)計(jì)成支持一組不相關(guān)地功能時(shí),我們說它具有低內(nèi)聚。內(nèi)聚是一個(gè)比單一責(zé)任更加普遍的概念,但兩者關(guān)系密切,遵守這原則的類容易具有很高的凝聚力,比低內(nèi)聚的類容易維護(hù)得多。

Vander在歡樂海岸的火鍋店的店租也越來越貴了,近兩個(gè)月甚至已經(jīng)入不敷出了,所以它決定擴(kuò)大pizza店的店面,加入燒烤之后再加入火鍋,這樣讓客人有更多地選擇,可惜地是廚師C的自助火鍋店的菜單又是用另一種數(shù)據(jù)結(jié)構(gòu)來完成的,不要緊,現(xiàn)在采用的系統(tǒng)已經(jīng)具有一定的擴(kuò)展性了。接下來添加火鍋店的Menu的。
HotPotMenu:

public class HotPotMenu implements Menu<MenuItem>{

    private Map<String, MenuItem> itemMap; 
    
    public HotPotMenu(){
        itemMap = new HashMap<String, MenuItem>();
        itemMap.put("lotus root", new MenuItem("lotus root", "with salt", 10));
        itemMap.put("tofu", new MenuItem("tofu", "with salt", 5));
        itemMap.put("potatos", new MenuItem("potatos", "with salt", 10));
    }
    
    public Iterator<MenuItem> createIterator() {
        HotPotMenuIterator hotPotMenuIterator = new HotPotMenuIterator(itemMap);
        return hotPotMenuIterator;
    }

}

HotPotMenuIterator:

public class HotPotMenuIterator implements Iterator<MenuItem>  {

    private Map<String, MenuItem> itemMap; 
    
    private Iterator<String> itemSetIterator;
    
    public HotPotMenuIterator(Map<String, MenuItem> itemMap) {
        this.itemMap = itemMap;
        itemSetIterator = itemMap.keySet().iterator();
    }
    
    public boolean hasNext() {
        if(itemSetIterator.hasNext()) {
            return true;
        }
        return false;
    }

    public MenuItem next() {
        MenuItem menuItem = itemMap.get(itemSetIterator.next());
        return menuItem;
    }

}

每次加入新的菜單,MenuAdmin都需要添加新的Menu,并且還要添加多一句遍歷新菜單的語句。我們將其進(jìn)一步進(jìn)行改進(jìn)。
MenuAdmin:

public class MenuAdmin {

    private List<Menu<MenuItem>> menuList;

    public MenuAdmin(List<Menu<MenuItem>> menuList) {
        this.menuList = menuList;
    }
    
    public void displayMenus() {
        for(Menu<MenuItem> menu : menuList) {
            traverseMenus(menu.createIterator());
        }
    }
    
    public void traverseMenus(Iterator<MenuItem> iterator) {
        while(iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                    + "-price:" + menuItem.getPrice());
        }
    }
    
}

最后又到了喜聞樂見的總結(jié)部分,我們又來總結(jié)我們現(xiàn)在現(xiàn)有的設(shè)計(jì)模式武器。

面向?qū)ο蠡A(chǔ)

抽象、封裝、多態(tài)、繼承

九大設(shè)計(jì)原則

設(shè)計(jì)原則一:封裝變化
設(shè)計(jì)原則二:針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程
設(shè)計(jì)原則三:多用組合,少用繼承
設(shè)計(jì)原則四:為交互對(duì)象之間的松耦合設(shè)計(jì)而努力
設(shè)計(jì)原則五:對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
設(shè)計(jì)原則六:依賴抽象,不要依賴于具體的類
設(shè)計(jì)原則七:只和你的密友談話
設(shè)計(jì)原則八:別找我,我有需要會(huì)找你
設(shè)計(jì)原則九:類應(yīng)該只有一個(gè)改變的理由

模式

迭代器模式:提供一個(gè)方法順序訪問一個(gè)聚合對(duì)象中的各個(gè)元素,而又不暴露其內(nèi)部的實(shí)現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。