本文主要是讀書筆記的整理,本身總結的倒很少,作個記錄html
若是能把多個普通類的對象聚在一塊兒造成一個整體,這個整體就被稱之爲彙集(Aggregate),舉例子:java
一、在任何編程語言中:數組都是最基本的彙集,在Java中,數組也是其餘的 JAVA 彙集對象的設計基礎。算法
二、在Java裏,JAVA彙集對象都是實現了 java.util.Collection 接口的對象,是 JAVA 對彙集概念的直接支持。從 JDK 1.2 開始,JAVA 提供了多種現成的彙集 API,包括 Vector、ArrayList、HashSet、HashMap、Hashtable、ConcurrentHashMap 等。編程
假如因業務須要,RD 定義了專屬的數據元素的彙集,還要把它提供給客戶端,讓其調用(不特別強調,也包括其餘依賴服務)。可是有時候爲了安全,RD 不想讓客戶端看到彙集的內部實現,只是能讓她們訪問就能夠了,好比遍歷等操做。還有的時候,客戶端不須要了解具體實現,可否讓客戶端跳開復雜的數據結構?由於調用者們不須要了解實現方式,只要能開箱即用便可。設計模式
爲了解決這個問題,那麼就須要有一種策略能讓客戶端遍歷這個彙集體的時候,沒法窺破RD存儲對象的方式,無需瞭解內部的複雜數據結構。數組
有兩個遺留的點餐系統,包括一套餐廳點餐系統——專門提供正餐,和一個煎餅鋪子點餐系統(不要糾結爲啥煎餅攤也有點餐系統。。。)——專門提供早餐(除了早餐,其餘時間不開放)。安全
餐廳裏有不少賣飯的窗口,它們的業務是一塊單獨的實現,隔壁煎餅鋪的業務,也是一塊單獨的實現。如今有個老闆想把它們收購併合併,讓客戶能在一個地方,一個時間段內,同時吃煎餅和餐廳的各類菜。目前餐廳內有至少兩家餐館都統一實現了 MenuItem 類——菜單子系統的菜單類。數據結構
可是煎餅的菜單系統用的 ArrayList 記錄菜單,而餐廳的 RD 用的是數組實現了菜單系統,雙方的RD,都不肯意花費時間修改本身的實現。畢竟有不少其餘服務依賴了菜單子系統,以下 MenuItem 代碼:app
/** * 餐廳的菜單都是午飯項目,煎餅的菜單,都是早餐項目,可是它們都屬於菜單,即: * 都有菜品名稱,描述,是不是素的,價格等 * 故設計這樣一個類做爲菜單項目類 */ public class MenuItem { String name; String description; public MenuItem(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } }
不一樣的餐廳使用了這個 MenuItem 類編程語言
/** * 煎餅窗口的菜單 */ public class PancakeHouseMenu { private List<MenuItem> menuItems; // menuItems 使用 ArrayList 存儲菜單的項目,動態數組,使其很容易擴大菜單規模 /** * 在構造菜單的時候,把菜單加入到 ArrayList menuItems */ public PancakeHouseMenu() { menuItems = new ArrayList<>(); addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast"); addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage"); } public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description); menuItems.add(menuItem); } public List<MenuItem> getMenuItems() { return menuItems; } } /////////////////////////////////////////////////////////////// /** * 餐廳的菜單 */ public class DinerMenu { private static final int MAX_ITEMS = 6; private int numberOfItems = 0; private MenuItem[] menuItems; // 使用了真正的數組實現菜單項的存儲 public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat"); addItem("BLT", "Bacon with lettuce & tomato on whole wheat"); addItem("Soup of the day", "Soup of the day, with a side of potato salad"); } public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description); if (numberOfItems >= MAX_ITEMS) { System.err.println("Sorry, menu is full! Can't add item to menu"); } else { menuItems[numberOfItems] = menuItem; numberOfItems = numberOfItems + 1; } } public MenuItem[] getMenuItems() { return menuItems; } }
兩種不一樣的菜單表現方式,會給客戶端調用帶來不少問題,假設客戶端是服務員類——Waitress,下面是客戶端的業務:
打印出菜單上的每一項:打印每份菜單上的全部項,必須調用 PancakeHouseMenu 和 DinerMenu 的 getMenuItem 方法,來取得它們各自的菜單項,可是二者返回類型是不同的
只打印早餐項(PancakeHouseMenu 的菜單)或者只打印午飯項(DinerMenu 的菜單)
想要打印 PancakeHouseMenu 的項,咱們用循環將早餐 ArrayList 內的項列出來
想要打印 DinerMenu 的項目,咱們用循環將數組內的項一一列出來
打印全部的素食菜單項
指定項的名稱,若是該項是素食的話,返回true,不然返回false
實現 Waitress 的其餘方法,作法都和上面的方法相似,發現 Waitress 處理兩個菜單時,老是須要寫兩個形式類似的循環,去遍歷這些菜單,並且一旦外部菜單的數據結構變了,客戶端也得跟着修改。
再有,若是還有第三家餐廳合併,並且坑爹的是,它以徹底不一樣的實現方式實現了菜單……那怎麼辦?此時難道還繼續寫第三個循環麼……
之後,這樣甚至能發展到 N 個不一樣形式的循環……
這顯然是很是很差的設計,直接致使後期系統的大量垃圾代碼和日益艱鉅的維護任務。
封裝特性 |
面向接口編程 |
代碼冗餘 |
---|---|---|
Waitress (也就是客戶端)居然能很是清晰的, 並且是必須清晰的熟悉服務端的實現,這是很不科學的 |
PancakeHouseMenu 和 DinerMenu 都沒有面向接口編程, 而直接實現了具體業務,致使擴展困難 |
DinerMenu和PancakeHouseMenu都有很大重複代碼, 沒有抽象共享 |
那麼能夠解決麼?
一、Waitress 要遍歷早餐項,須要使用 ArrayList 的 size() 和 get() 方法
二、Waitress 遍歷午飯項,須要使用數組的 length 字段和中括號
如今建立一個新的對象,將它稱爲迭代器(Iterator),利用它來封裝「遍歷集合內的每一個對象的過程」,下面對其抽象、封裝。
案例中變化的部分:由於不一樣的集合實現,致使的不一樣的遍歷方式。將其封裝便可,其實,這正是迭代器模式的應用。迭代器 Iterator,是面向接口編程,故它依賴於一個稱爲迭代器的接口:
/** * 迭代器的接口,一旦有了這個接口,就能夠爲給種對象集合實現迭代器:數組、列表、散列表等等 */ public interface Iterator { /** * 彙集中,是否還有元素 */ boolean hasNext(); /** * 返回彙集中的下一個元素 */ Object next(); }
讓餐廳實現迭代器接口 —— Iterator,打造一個餐廳菜單迭代器——DinerMenuIterator
/** * 餐廳的迭代器 */ public class DinerMenuIterator implements Iterator { private MenuItem[] items; private int position = 0; public DinerMenuIterator(MenuItem[] items) { this.items = items; } public Object next() { MenuItem menuItem = items[position]; position = position + 1; return menuItem; } public boolean hasNext() { return position < items.length && items[position] != null; } }
改造具體餐廳的菜單舊實現,把以前的以下代碼刪掉,由於它會暴露餐廳菜單的內部數據結構 menuItems
public MenuItem[] getMenuItems() { return menuItems; }
下面是改造以後的餐廳菜單實現,PancakeHouseMenu 實現相似。
public class DinerMenu { private static final int MAX_ITEMS = 6; private int numberOfItems = 0; private MenuItem[] menuItems; // 實現方式不變 public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat"); addItem("BLT", "Bacon with lettuce & tomato on whole wheat"); addItem("Soup of the day", "Soup of the day, with a side of potato salad"); } // 實現方式不變 public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description, vegetarian, price); if (numberOfItems >= MAX_ITEMS) { System.err.println("Sorry, menu is full! Can't add item to menu"); } else { menuItems[numberOfItems] = menuItem; numberOfItems = numberOfItems + 1; } } // 不須要 getMenuItems 方法,由於它會暴露內部實現,返回的直接是菜單的數據結構 // 這個新方法代替 getMenuItems,createIterator 返回的是迭代器接口 public Iterator createIterator() { return new DinerMenuIterator(menuItems); } }
這樣寫客戶端的代碼就不會重複兩遍,以下,把迭代器的代碼整合到 Waitress,改掉以前冗餘的循環遍歷代碼,只須要傳入一個迭代器做爲遍歷方法的參數,把遍歷彙集的工做,委託給迭代器實現。既能保護內部實現,也能抽象遍歷形式,精簡代碼。也符合了開閉原則——之後菜單的實現邏輯修改了,客戶端也不用修改調用的代碼。
public class Waitress { // 服務員依賴的菜單系統 private PancakeHouseMenu pancakeHouseMenu; private DinerMenu dinerMenu; public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu; } /** * 遍歷所有菜單,無需在客戶端裏積壓多個重複的循環代碼,也符合了開閉原則——之後修改遍歷邏輯,客戶端不須要修改 */ public void printMenu() { // 爲每一個菜單系統,建立一個迭代器 Iterator Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator();// 把迭代器子類型,傳入 printMenu(pancakeIterator);// 把迭代器子類型,傳入 printMenu(dinerIterator); } /** * 接口的用法,向上轉型 */ private void printMenu(Iterator iterator) { // 先判斷是否還能繼續迭代 while (iterator.hasNext()) { // Iterator 接口裏 next 返回的是 Object 對象,故須要強制轉換 MenuItem menuItem = (MenuItem) iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.println(menuItem.getDescription()); } } } ////// public class MenuTestDrive { public static void main(String args[]) { PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu(); DinerMenu dinerMenu = new DinerMenu(); Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu); waitress.printMenu(); } }
經迭代器模式對菜單系統進行封裝,使得各個餐廳的菜單系統能維持不變,磨平了實現的差異,減小了重寫的工做量。
舊版代碼的客戶端 |
基於迭代器模式封裝服務後,重寫的客戶端 |
---|---|
遍歷:須要多個代碼重複度較高的循環來實現,代碼冗餘度很高,加大無心義的工做量 |
只須要增長類,去實現各個菜單系統的迭代器,客戶端只須要一個循環就能搞定全部的菜單服務調用 |
各個菜單系統的具體實現,封裝的不行,對客戶端暴露了數據結構,這是沒有任何須要的 |
菜單的具體實現被封裝,對外只公開迭代器,客戶端不知道,也不須要知道具體菜單的實現 |
客戶端被捆綁到了多個菜單實現類,牽一髮動全身 |
客戶端能夠只用 iterator 接口作參數,經過向上轉型,擺脫多個具體實現的捆綁,實現解耦 |
客戶端 Waitress 組合了多個具體實現類,仍然會牽一髮動全身,好比修改了菜單的類名,客戶端就失效,也須要修改,仍然重度依賴
並且,具體菜單的實現類又有共同的方法 createIterator ,徹底能夠進一步抽象。
首先再也不爲List這樣的數據結構從新實現迭代器,由於JDK 5 以後,Java 已經給咱們實現好了,對於JDK 5 以後的全部集合容器,均可以採用 JDK 自帶的迭代器接口——java.util,Itreator,因此咱們就不用本身寫,只需實現數組的迭代器便可。
一、記住:JDK 不支持爲數組生成迭代器
二、java.util 包中的 Collection 接口——Java 全部的集合都實現了該接口,該接口有迭代器方法。
能夠爲各個菜單實現類,提供一個公共的接口——Menu
有多個具體實現類的時候,要首先考慮不針對實現編程,而是面向接口編程,除非有共同的抽象方法+屬性時,能夠考慮抽象父類。本案例中,只需使用接口,就能夠減小客戶端 waittress 和具體菜單實現類之間的依賴。
import java.util.Iterator; /** * 菜單系統要實現的方法,抽象爲接口 */ public interface Menu { Iterator createIterator(); } ///////////////////////////// /** * 菜單的每項,抽象爲類 */ public class MenuItem { private String name; private String description;public MenuItem(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } } ///////////////////////////////// // 餐廳菜單系統的迭代器,不須要實現額外聲明的迭代器接口,而是重寫JDK的迭代器便可 import java.util.Iterator; /** * 重寫 JDK 的迭代器 * implements java.util.Iterator; */ public class DinerMenuIterator implements Iterator { private MenuItem[] items; private int position = 0; public DinerMenuIterator(MenuItem[] items) { this.items = items; } // 不須要變 @Override public Object next() { MenuItem menuItem = items[position]; position = position + 1; return menuItem; } // 不須要變 @Override public boolean hasNext() { return position < items.length && items[position] != null; } // 從新實現,最好是重寫 @Override public void remove() { if (position <= 0) { throw new IllegalStateException ("You can't remove an item until you've done at least one next()"); } // 刪除線性表的元素,全部元素須要往前移動一個位置 if (items[position - 1] != null) { for (int i = position - 1; i < (items.length - 1); i++) { items[i] = items[i + 1]; } items[items.length - 1] = null; } } } //////////////////// // 餐廳菜單系統 import java.util.Iterator; /** * Created by wangyishuai on 2018/1/27 */ public class DinerMenu implements Menu { private static final int MAX_ITEMS = 6; private int numberOfItems = 0; private MenuItem[] menuItems; // 實現方式不變 public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("Soup of the day", "Soup of the day, with a side of potato salad"); } // 實現方式不變 public void addItem(String name, String description, boolean vegetarian, double price) { MenuItem menuItem = new MenuItem(name, description, vegetarian, price); if (numberOfItems >= MAX_ITEMS) { System.err.println("Sorry, menu is full! Can't add item to menu"); } else { menuItems[numberOfItems] = menuItem; numberOfItems = numberOfItems + 1; } } // 返回的是 java.util.Iterator; @Override public Iterator createIterator() { return new DinerMenuIterator(menuItems); } } ///////////////////////////////////////// // 煎餅,不須要再實現迭代器,由於使用的數據結構是JDK的容器,而對於JDK自帶的集合容器,不須要本身實現迭代器 import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 對於JDK的集合容器——List,不須要RD實現迭代器 */ public class PancakeHouseMenu implements Menu { private List<MenuItem> menuItems; public PancakeHouseMenu() { menuItems = new ArrayList<>(); addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast"); } public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description); menuItems.add(menuItem); } @Override public Iterator createIterator() { // 返回 JDK ArrayList 自帶的迭代器 iterator() 方法 return menuItems.iterator(); } } //////////////////////////////// 客戶端 import java.util.Iterator; public class Waitress { // 服務員依賴的菜單系統——經過接口解耦合 private Menu pancakeHouseMenu; private Menu dinerMenu; // 修改成 Menu 接口,向上轉型,解耦合 public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu; } /** * 之後修改遍歷邏輯,客戶端不須要修改 * // 不用修改 */ public void printMenu() { // 爲每一個菜單系統,建立迭代器 // java.util.Iterator; Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator(); printMenu(pancakeIterator); printMenu(dinerIterator); } /** * 接口的用法,向上轉型 * // 不用修改 * java.util.Iterator; */ private void printMenu(Iterator iterator) { // 先判斷是否還能繼續迭代 while (iterator.hasNext()) { // Iterator 接口裏 next 返回的是 Object 對象,故須要強制轉換 MenuItem menuItem = (MenuItem) iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.println(menuItem.getDescription()); } } } public class MenuTestDrive { public static void main(String args[]) { Menu pancakeHouseMenu = new PancakeHouseMenu(); Menu dinerMenu = new DinerMenu(); // 即便具體的菜單實現類修改了名字或者環了實現類,客戶端——Waitress 也不須要修改代碼,解了耦合 Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu); waitress.printMenu(); } }
雖然對於客戶端來講,remove 方法非必須(固然業務須要的話,就必須自定義重寫 remove),可是最好仍是提供該方法,由於JDK的 Iterator接口裏包含了該方法,若是不一塊兒重寫,可能會出問題。
若是客戶端真的不須要刪除元素,那麼最好也重寫該方法,只須要在重寫的時候拋出一個自定義的(或者現成的)異常——若是有調用,就提醒客戶端不能刪除元素。JDK也是這樣設計的,默認拋出異常 UnsupportedOperationException
默認的迭代器接口是線程不安全的,若是有須要,要額外的增強線程安全。
迭代器模式又叫遊標(Cursor)模式、Iterator模式,迭代子模式……是對象的行爲模式之一,它把對容器中包含的內部對象的訪問委託給外部的類,讓外部的類可使用 Iterator 按順序進行遍歷訪問,而又不暴露其內部的數據結構。
Iterator Pattern (Another Name: Cursor)
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
脫離Java的領域,那麼能夠認爲:迭代器模式能夠順序地訪問彙集中的元素,而沒必要暴露彙集的內部狀態(internal representation)。它把遍歷的責任轉移到了迭代器,而不是彙集自己,簡化了彙集的接口和實現代碼,也分割了責任。
Iterator(迭代器接口):該接口必須定義實現迭代功能的最小定義方法集,好比提供hasNext()和next()方法。
ConcreteIterator(具體的迭代器實現類):迭代器接口Iterator的實現類。能夠根據具體狀況加以實現。
Aggregate(彙集的接口):定義基本功能以及提供相似Iterator iterator()的方法。
concreteAggregate(彙集接口的實現類):容器接口的實現類。必須實現生成迭代器的方法。
若是是自定義的彙集,那麼須要由彙集本身實現順序遍歷的方法——直接在彙集的類裏添加遍歷方法。這樣,容器類承擔了太多功能:
一方面須要提供添加、刪除等自己應有的功能;
一方面還須要提供遍歷訪問功能。
不只責任不分離,還和客戶端耦合太強
若是不使用迭代器模式,那麼須要客戶端本身實現服務的遍歷(聯繫餐廳和煎餅屋的合併案例),會直接暴露彙集的數據結構,每每這是沒必要要的,客戶端不須要了解服務的具體實現,也是爲了程序的安全——不暴露太多的內部細節給客戶端。
若是使用的是 JDK 的集合類,若是直接遍歷,且遍歷的時候對集合修改,會有異常拋出。由於,每每容器在實現遍歷的過程當中,須要保存遍歷狀態,當遍歷操做和元素的添加、刪除等操做夾雜在一塊兒,這些更新功能在遍歷的時候也被調用,很容易引發集合的狀態混亂和程序運行錯誤等。此時應該爲彙集使用迭代器模式,若是是JDK的集合類,就直接使用自帶的迭代器進行迭代。
記住:Java 中的 foreach 循環看起來像一個迭代器,但實際上並非,仍是要使用迭代器模式
Iterator 支持從源集合中安全地刪除對象,只需在 Iterator 上調用 remove() 便可。這樣作的好處是能夠避免 ConcurrentModifiedException ,這個異常顧名思意:當打開 Iterator 迭代集合時,同時又在對集合進行修改。有些集合不容許在迭代時刪除或添加元素,可是調用 Iterator 的remove() 方法是個安全的作法。
List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d")); for(String s : list){ if(s.equals("a")){ list.remove(s); } } //會拋出一個ConcurrentModificationException異常,相反下面的顯示正常 List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d")); Iterator<String> iter = list.iterator(); while(iter.hasNext()){ String s = iter.next(); if(s.equals("a")){ iter.remove(); } } // next() 必須在 remove() 以前調用。 // 在 foreach 中,編譯器會使 next() 在刪除元素以後被調用,所以就會拋出 ConcurrentModificationException 異常
參考
一、Iterator的remove方法可保證從源集合中安全地刪除對象(轉)
二、正確遍歷刪除List中的元素方法 http://www.jb51.net/article/98763.htm
若是一個算法開始以後,它的運算環境發生變化,使得算法沒法進行必需的調整時,這個算法就應當當即發出故障信號。這就是 Fail Fast 的含義。同理,若是彙集的元素在一個動態迭代子的迭代過程當中發生變化,迭代過程會受到影響而變得不能自恰。這時候,迭代子就應當即拋出一個異常。這種迭代子就是實現了Fail Fast 功能的迭代子。
Iterator 模式就是爲了有效地處理按順序進行遍歷訪問的一種設計模式,簡單地說,Iterator模式提供一種有效的方法,能夠屏蔽彙集對象的容器類的實現細節,而能對容器內包含的對象元素按順序進行有效的遍歷訪問。因此,Iterator模式的應用場景能夠概括爲如下幾個:
訪問容器中包含的內部對象
按順序訪問
優勢總結:
1,實現功能分離,簡化彙集的接口。讓彙集只實現自己的基本功能,把迭代功能委託給外部類實現,符合類的單一職責設計原則。
2,隱藏彙集的實現細節,符合最小知道原則。爲彙集或其子容器提供了一個統一接口,一方面方便客戶端調用;另外一方面使得客戶端沒必要關注迭代器的實現細節。
3,能夠爲彙集或其子容器實現不一樣的迭代器,搭配其餘設計模式,好比策略模式等,能夠很容易的切換。
四、客戶端能夠同時使用多個迭代器遍歷一個彙集。
截止到此處,都是分析的外部迭代器模式——客戶端來調用 next 方法,去取得下一個元素。
相反,內部迭代器是由迭代器本身控制遊標,在這種狀況下,必須告訴迭代器在遊標移動的過程當中,要作什麼事情——必須將操做傳給迭代器,由於內部迭代器的客戶端,沒法控制遍歷過程,所欲內部迭代器伸縮性不強,通常不使用。
都知道,next 方法是正向遍歷,那麼天然能夠實現反向遍歷,新加一個取得前一個元素的方法 + 一個判斷遊標是否已經走到了首節點的方法便可解決。
JDK也爲咱們作了實現:ListIterator接口,提供了一個previous方法,JDK中的任何實現了List接口的集合,均可以實現反向迭代。
澄清一個問題——迭代器模式是沒有約束元素順序的,即 next (previous)只是取出元素,並非強制元素取出的前後順序等價於元素的某種排序。通俗的說,不管是線性結構仍是非線性的,甚至是包含重複元素的結構,除非有特殊業務需求,都能對其實現迭代器模式。
不可幻想:迭代的順序就等價於集合中元素的某種有意義的排序,二者沒有必然關係,謹記以免作出錯誤判斷,除非有自定義的順序約束。
設計原則:一個類只有一個引發變化的緣由。若是有一個類具備兩個改變的緣由,那麼這會使得未來該類的變化機率上升,而當它真的改變時,你的設計中同時又有兩個方面將會受到影響。
內聚:用來度量一個類或者模塊緊密的達到了單一職責的目的(or 責任)。當一個類或者一個模塊被設計爲只支持一組相關的功能的時候,就說它具備高內聚的特性,反之就是低內聚的。
高內聚是一個比單一職責更廣泛的概念,即遵照了高內聚的類,也一樣具備單一職責。
其實前面的分析已經很全面,迭代器模式,分離了彙集的迭代的責任,有效的契合了單一職責設計原則。
爲其合併後的系統,增長咖啡廳的菜單,供應晚餐。下面是咖啡廳的菜單系統實現:
import java.util.HashMap; import java.util.Map; /** * 原始的咖啡廳菜單實現類 */ public class CafeMenu { /** * 菜單使用了hash表存儲,和現有的兩個菜單系統實現不同 */ private Map<String, MenuItem> menuItems = new HashMap<>(); public CafeMenu() { addItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries"); } public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description); menuItems.put(menuItem.getName(), menuItem); } public Map<String, MenuItem> getItems() { return menuItems; } } ////////////////////////////////////////////////// public class MenuItem { private String name; private String description;public MenuItem(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } }
將咖啡廳菜單系統合併到現有的系統:
public interface Menu { Iterator createIterator(); } //////////////////////// 咖啡廳菜單系統 import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * 合併以後的咖啡廳菜單實現類 * hash表也實現了JDK的迭代器,不須要RD本身實現 */ public class CafeMenu implements Menu { /** * 菜單使用了hash表存儲,和現有的兩個菜單系統實現不同 * 實現不變 */ private Map<String, MenuItem> menuItems = new HashMap<>(); // 實現不變 public CafeMenu() { addItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries"); } // 實現不變 public void addItem(String name, String description) { MenuItem menuItem = new MenuItem(name, description); menuItems.put(menuItem.getName(), menuItem); } /** * hash表支持JDK自帶的迭代器 java.util.Iterator; */ @Override public Iterator createIterator() { // 返回 java.util.Iterator; 只須要取得 hash 表的 value 集合便可 return menuItems.values().iterator(); } } //////////////////////// 客戶端 Waitress import java.util.Iterator; public class Waitress { private Menu pancakeHouseMenu; private Menu dinerMenu; // 須要增長 cafeMenu private Menu cafeMenu; /** * 若是,太多的參數,可使用建造者模式優化構造器 * 須要增長 cafeMenu 參數 */ public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu; this.cafeMenu = cafeMenu; } /** * 須要增長 cafeMenu 的迭代器 */ public void printMenu() { Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator(); Iterator cafeIterator = cafeMenu.createIterator(); printMenu(pancakeIterator); printMenu(dinerIterator); printMenu(cafeIterator); } /** * 無需修改 */ private void printMenu(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem) iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print(menuItem.getPrice() + " -- "); System.out.println(menuItem.getDescription()); } } } //////////////////////// 測試 public class MenuTestDrive { public static void main(String args[]) { Menu pancakeHouseMenu = new PancakeHouseMenu(); Menu dinerMenu = new DinerMenu(); Menu cafeMenu = new CafeMenu(); Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu); waitress.printMenu(); } }
合併咖啡廳的過程當中,發現每次合併新菜單,都要打開客戶端,修改代碼……客戶端實現很醜陋,違反了開閉原則。
雖然咱們抽象了菜單,讓其在客戶端解耦,而且爲菜單系統分別實現了迭代器,讓迭代責任分離,對客戶端隱藏了具體實現,使用同一的迭代器接口,解耦了迭代動做。可是,仍然將菜單處理分紅獨立的對象看待,致使每次擴展,都須要修改客戶端——客戶端須要反覆寫:調用printMenue的代碼,代碼冗餘嚴重,並且每次都要給構造器增長新參數。
須要一種更好的辦法——集中管理菜單,使其使用一個迭代器便可應付菜單的擴展
使用現成的 ArrayList 類實現:
import java.util.Iterator; import java.util.List; public class Waitress1 { /** * 把各個菜單系統集中到一個list,充分利用list的迭代器 * 只須要一個類就搞定,再也不每次都add一個菜單類了 */ private List<Menu> menus; public Waitress1(List<Menu> menus) { this.menus = menus; } public void printMenu() { // 取得list的迭代器,直接使用一個迭代器,就能遍歷全部菜單,不須要在修改 Iterator menuIterator = menus.iterator(); while (menuIterator.hasNext()) { Menu menu = (Menu) menuIterator.next(); printMenu(menu.createIterator()); } } // 代碼不須要變 void printMenu(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem) iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print(menuItem.getPrice() + " -- "); System.out.println(menuItem.getDescription()); } } }
如今但願可以加上一份餐後甜點「子菜單」做爲晚餐的飯後補充。若是咱們能讓甜點菜單變成餐廳菜單集合的一個子元素,就能夠完美的解決。可是根據如今的實現,根本作不到。由於飯後甜點子菜單的實現基於數組——不變的,類型不一樣,沒法擴展。生產環境中,這樣的系統很是複雜,更加困難。
一、須要某種樹形結構,能夠容納菜單、子菜單和菜單項。
二、須要肯定可以在每一個菜單的各個項目之間遊走,並且至少要像如今用迭代器同樣方便。
三、須要可以更有彈性地在菜單項之間遊走。比方說:可能只須要遍歷甜點菜單,或者能夠遍歷餐廳的整個菜單。
此時,須要一種新的設計模式來解決這個案例的難題——組合模式,參看:優雅的處理樹狀結構——組合模式總結