你項目組接到一個項目:對電視機的電視頻道、電影和收音機菜單進行統一管理,建立一個統一的菜單管理界面,能夠看到所有的電視界面、電影界面和收音機頻道。你有三個手下:小李子、小杏子、小安子,他們分別就每個模塊做開發工作,看他們都做了哪些工作。
這是主菜單JavaBean,用於顯示每個模塊的菜單。
public class MenuItem { private String name; private String description; private int channe; public MenuItem(int channe,String name,String description){ this.name = name; this.description = description; this.channe = channe; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public int getChanne() { return channe; } public void setChanne(int channe) { this.channe = channe; } }
小李子負責電視頻道菜單的實現。他是使用List實現的,他認為這樣就可以非常方便的擴展菜單。
public class TVChanneMenu implements TelevisionMenu{ List<MenuItem> menuItems; /** * 構造函數完成初始化 */ public TVChanneMenu(){ menuItems = new ArrayList<MenuItem>(); addItem(1, "CCTV-1", "This is CCTV-1"); addItem(2, "CCTV-2", "This is CCTV-2"); addItem(3, "CCTV-3", "This is CCTV-3"); addItem(4, "CCTV-4", "This is CCTV-4"); addItem(5, "CCTV-5", "This is CCTV-5"); } /** * @desc 將電視頻道節目添加菜單集合中 * @param channe 頻道 * @param name 名稱 * @param description 描述 * @return void */ public void addItem(int channe,String name,String description){ MenuItem tvMenuItem = new MenuItem(channe, name, description); menuItems.add(tvMenuItem); } public List<MenuItem> getMenuItems() { return menuItems; } }
小杏子負責電影菜單模塊的開發。她是使用數組完成的,他認為數組的效率高些,而且能夠控制菜單的長度。
public class FilmMenu implements TelevisionMenu{ static final int MAX_ITEMS = 5; //菜單最大長度 MenuItem[] menuItems; int numberOfItems = 0; /** * 構造函數完成初始化 */ public FilmMenu(){ menuItems = new MenuItem[MAX_ITEMS]; addItem(1, "絕世天劫", "這是布魯斯威利斯主演的..."); addItem(2, "達芬奇密碼", "這是我最喜歡的電影之一..."); addItem(3, "黑客帝國123", "不知道你能夠看懂不???"); addItem(4, "我的女友是機器人", "一部我不反感的經典愛情電影..."); addItem(5, "肖申克的救贖", "自由,幸福,離你有多遠"); } /** * @desc 將電影解決添加到菜單項中 * @param channe * @param name * @param description * @return void */ public void addItem(int channe,String name,String description){ MenuItem tvmenuiItem = new MenuItem(channe, name, description); //判斷數組是否越界 if(numberOfItems > MAX_ITEMS){ System.out.println("不好意思,菜單滿了...."); } else{ menuItems[numberOfItems] = tvmenuiItem; numberOfItems ++; } } public MenuItem[] getMenuItems() { return menuItems; } }
這里就只介紹兩個了。他們完成了各自菜單功能的實現,當你在做三個菜單的統一顯示時,你必須要要調用他們的getMenuItems(),來取得他們各個菜單里面的值,但是這個時候問題出現了,你發現他們的返回值都不相同,要想展示出來你必須這么干。
List<MenuItem> tvMenuItems = tvMenu.getMenuItems(); for(int i = 0 ; i < tvMenuItems.size() ; i++){ MenuItem menuItem = tvMenuItems.get(i); ............ } FilmMenu filmMenu = new FilmMenu(); MenuItem[] filmItems = filmMenu.getMenuItems(); for(int i = 0 ; i < filmItems.length ; i++){ MenuItem menuItem = filmItems[i]; ............ }
在這里我們總是需要來處理這個兩個菜單,通過循環來遍歷這些菜單選項,如果小安子的實現也不同呢?是不是得要三個循環呢?如果下次有需要增加一個模塊呢?在這里你就惱火了,他們為什么不采用相同的實現方式,但是這個時候你也不可能讓他們去更改了,他們有太多的代碼在依賴着這個兩個菜單了,如果更改就意味着要重寫很多的代碼了。所以你就想是不是可以來封裝循環呢?不錯就是封裝遍歷。這就是迭代器模式的動機--能夠游走於聚合內的每一個元素,同時還可以提供多種不同的遍歷方式。
一、模式定義
何謂迭代器模式?所謂迭代器模式就是提供一種方法順序訪問一個聚合對象中的各個元素,而不是暴露其內部的表示。在實際的開發過程中,我們可能需要針對不同的需求,可能需要以不同的方式來遍歷整個整合對象,但是我們不希望在聚合對象的抽象接口層中充斥着各種不同的便利操作。這個時候我們就需要這樣一種東西,它應該具備如下三個功能:
1、能夠便利一個聚合對象。
2、我們不需要了解聚合對象的內部結構。
3、能夠提供多種不同的遍歷方式。
這三個功能就是迭代器模式需要解決的問題。作為一個功能強大的模式,迭代器模式把在元素之間游走的責任交給迭代器,而不是聚合對象。這樣做就簡化了聚合的接口和實現,也可以讓聚合更專注在它所應該專注的事情上,這樣做就更加符合單一責任原則。
二、模式結構
下圖是迭代器模式UML圖。
從上面可以看書迭代器模式有如下幾個角色:
Iterator: 抽象迭代器:所有迭代器都需要實現的接口,提供了游走聚合對象元素之間的方法。
ConcreteIterator: 具體迭代器。利用這個具體的迭代器能夠對具體的聚合對象進行遍歷。每一個聚合對象都應該對應一個具體的迭代器。
Aggregate: 抽象聚合類。
ConcreteAggregate: 具體聚合類。實現creatorIterator()方法,返回該聚合對象的迭代器。
三、模式實現
下面利用迭代器模式對上面案例進行優化整改。下面是正對該實例的UML圖。
首先我們需要定義迭代器接口。Iterator.java
public interface Iterator { boolean hasNext(); Object next(); }
然后是我們兩個具體的迭代器。一個迭代器遍歷電視界面、一個迭代器遍歷電影界面。
電影節目的迭代器:FilmMenuIterator.java
public class FilmMenuIterator implements Iterator{ MenuItem[] menuItems; int position = 0; public FilmMenuIterator(MenuItem[] menuItems){ this.menuItems = menuItems; } public boolean hasNext() { if(position > menuItems.length-1 || menuItems[position] == null){ return false; } return true; } public Object next() { MenuItem menuItem = menuItems[position]; position ++; return menuItem; } }
電視界面的迭代器:TVChanneMenuIterator.java
public class TVChanneMenuIterator implements Iterator{ List<MenuItem> menuItems; int position = 0; public TVChanneMenuIterator(List<MenuItem> menuItems){ this.menuItems = menuItems; } public boolean hasNext() { if(position > menuItems.size()-1 || menuItems.get(position) == null){ return false; } else{ return true; } } public Object next() { MenuItem menuItem = menuItems.get(position); position ++; return menuItem; } }
然后是菜單接口,該接口提供返回具體迭代器的方法:createIterator()。
public interface TelevisionMenu { public void addItem(int channe,String name,String description); public Iterator createIrerator(); }
兩個具體聚合類。這個兩個聚合類實現createIterator()方法,分別返回電視界面的聚合類和電影界面的聚合類。
public class FilmMenu implements TelevisionMenu{ static final int MAX_ITEMS = 5; //菜單最大長度 MenuItem[] menuItems; int numberOfItems = 0; /** * 構造函數完成初始化 */ public FilmMenu(){ menuItems = new MenuItem[MAX_ITEMS]; addItem(1, "絕世天劫", "這是布魯斯威利斯主演的..."); addItem(2, "達芬奇密碼", "這是我最喜歡的電影之一..."); addItem(3, "黑客帝國123", "不知道你能夠看懂不???"); addItem(4, "我的女友是機器人", "一部我不反感的經典愛情電影..."); addItem(5, "肖申克的救贖", "自由,幸福,離你有多遠"); } /** * @desc 將電影解決添加到菜單項中 * @param channe * @param name * @param description * @return void */ public void addItem(int channe,String name,String description){ MenuItem tvmenuiItem = new MenuItem(channe, name, description); //判斷數組是否越界 if(numberOfItems > MAX_ITEMS){ System.out.println("不好意思,菜單滿了...."); } else{ menuItems[numberOfItems] = tvmenuiItem; numberOfItems ++; } } public Iterator createIrerator() { return new FilmMenuIterator(menuItems); } }
public class TVChanneMenu implements TelevisionMenu{ List<MenuItem> menuItems; /** * 構造函數完成初始化 */ public TVChanneMenu(){ menuItems = new ArrayList<MenuItem>(); addItem(1, "CCTV-1", "This is CCTV-1"); addItem(2, "CCTV-2", "This is CCTV-2"); addItem(3, "CCTV-3", "This is CCTV-3"); addItem(4, "CCTV-4", "This is CCTV-4"); addItem(5, "CCTV-5", "This is CCTV-5"); } /** * @desc 將電視頻道節目添加菜單集合中 * @param channe 頻道 * @param name 名稱 * @param description 描述 * @return void */ public void addItem(int channe,String name,String description){ MenuItem tvMenuItem = new MenuItem(channe, name, description); menuItems.add(tvMenuItem); } public Iterator createIrerator() { return new TVChanneMenuIterator(menuItems); } }
終於完成了,現在就可以來實現主菜單了,用來展示、遍歷所有的電視、電影界面咯。
public class MainMenu { TelevisionMenu tvMenu; FilmMenu filmMenu; public MainMenu(TelevisionMenu tvMenu,FilmMenu filmMenu){ this.tvMenu = tvMenu; this.filmMenu = filmMenu; } public void printMenu(){ Iterator tvIterator = tvMenu.createIrerator(); Iterator filmIterator = filmMenu.createIrerator(); System.out.println("電視節目有:"); printMenu(tvIterator); System.out.println("----------------------------------------------------------------"); System.out.println("電影節目有:"); printMenu(filmIterator); } private void printMenu(Iterator iterator) { while(iterator.hasNext()){ MenuItem menuItem = (MenuItem) iterator.next(); System.out.print("channe:"+menuItem.getChanne()+", "); System.out.print("name:"+menuItem.getName()+", "); System.out.println("description :"+menuItem.getDescription()); } } }
測試程序:
public class Test { public static void main(String[] args) { TVChanneMenu tvMenu = new TVChanneMenu(); FilmMenu filmMenu = new FilmMenu(); MainMenu mainMenu = new MainMenu(tvMenu, filmMenu); mainMenu.printMenu(); } }
運行結果
四、模式優缺點
優點
1、它支持以不同的方式遍歷一個聚合對象。
2、迭代器簡化了聚合類。
3、在同一個聚合上可以有多個遍歷。
4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點
由於迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性。
五、模式使用場景
1、訪問一個聚合對象的內容而無須暴露它的內部表示。
2、需要為聚合對象提供多種遍歷方式。
3、為遍歷不同的聚合結構提供一個統一的接口。
六、模式總結
1、迭代器模式提供一種方法來訪問聚合對象,而不用暴露這個對象的內部表示。
2、將遍歷聚合對象中數據的行為提取出來,封裝到一個迭代器中,通過專門的迭代器來遍歷聚合對象的內部數據,這就是迭代器模式的本質。迭代器模式是“單一職責原則”的完美體現。
3、當使用迭代器的時候,我們依賴聚合提供遍歷。
4、迭代器提供了一個通用的接口,讓我們遍歷聚合的項,放我們編碼使用聚合項時,就可以使用多態機制。