1. 程式人生 > >設計模式——10 迭代器模式

設計模式——10 迭代器模式

10 Iterator Pattern(迭代器模式)

         前言:幫助客戶用同樣的方法遍歷不同的集合。

需求:

         由於海岸城的店租極具增長,Vander的Pizza店和燒烤店需要合併在一起了,合併在一起,這兩家店的主廚A和主廚B,他們的選單實現卻不相同,Pizza店的選單是使用List,而燒烤店的選單用的卻是陣列,讓我們先來看看他們選單的實現。

         首先兩個主廚都是使用了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結構來存放選單專案的,而BarbecueMenu則是用陣列來存放選單專案的。Vander作為老闆又是他大顯神威的時候了,他需要做一個選單綜合顯示平臺,顯示出PizzaMenu和BarbecueMenu。我們來看看Vander的實現:

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++) {//陣列的大小為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());
		}

	}

}

 實現效果:

Vander這麼設計真的好嗎?

1MenuAdmin是不是直接針對PizzaMenuBarbecueMenu進行編碼的,而不是針對介面程式設計。

2、如果又加入了日式料理選單,而選單是用HashMap實現的,豈不是又得需要修改很多MenuAdmin的程式碼。

3MenuAdmin直接關心了PizzaMenuBarbecueMenu的內部實現了,違反了封裝原則。

4displayMenu部分有重複程式碼,有兩個類似的迴圈,如果加入第三個選單又需要多一個迴圈。

Panda大師一看,又是糟糕的設計,Vander你就不能找一個迭代器來遍歷這些資料嗎,不同的資料結構實現不同的迭代器,MenuAdmin只需要用同樣的迴圈就可以遍歷這些選單項了。接著Panda開始寫迭代器(Iterator介面)。

Iterator(迭代器)介面:

public interface Iterator<T> {

	/**
	 * 判斷集合是否有下一項
	 * @return
	 */
	boolean hasNext();
	
	/**
	 * 獲取集合的下一項
	 * @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());
		}
	}
	
}

 下面是迭代器實現的基礎類圖:

這裡可能會有幾個問題:

         1、能不能直接用BarbecueMenu直接實現迭代器介面?

         答案是可以的,但是程式碼侵入性太大,我們要保證原有類不進行太大的變動,再說了廚師們也不喜歡你亂搞。

         2、MenuAdmin中的兩個屬效能不能直接是兩個選單對應的迭代器?

   答案也是可以的,但是這樣雖然實現上沒有問題,但是迭代器作為較為低層的部分,這麼做在邏輯上說不過去,本來選單管理的類裡面就應該是選單管理的,突然屬性變成了迭代器,這就有點奇怪了。

分析:

實際上當前的MenuAdmin依然是針對實現程式設計的,PizzaMenuBarbecueMenu都有createIterator方法,所以可以定義一個Menu介面,java.util中的ArrayList實際上已經幫我們實現了Iterator了,我們直接用就可以了。接下來,進行下一步改造。

我們首先刪除我們自己定義的Iterator介面和PizzaMenuIterator(由於ArrayList本身就有迭代器實現),然後importjava.util.Iterator介面,然後將MenuAdmin中的實現改成介面,改寫PizzaMenu類中的createIterator方法就完成了。

Menu

public interface Menu<T> {

	/**
	 * 建立迭代器
	 * @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不再針對於實現程式設計,而是針對介面(Menu)程式設計,減少了其對具體類的依賴。

2、每個選單都要求要實現Menu介面,所以每個選單必須提供createIterator方法,讓選單管理類能夠獲得Iterator。MenuAdmin類也不需要關心每個選單內部用了什麼結構去儲存資料,只需要用迭代器的方法就能完成遍歷。

下面我們隆重有請迭代器模式登場:

迭代器模式:提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的實現。迭代器模式把元素之間的遊走的責任交給迭代器,而不是聚合物件,這不僅讓聚合的介面和實現變得更簡潔,也可以讓聚合更專注在它所應該專注的事情(也就是管理物件集合),而不必去理會遍歷的事情。

迭代器的套路如下圖所示,這裡的Client對應MenuAdmin,ConcreteInterator對應PizzaMenuIterator和BarbecueMenuIterator,Aggregate對應Menu介面,ConcreteAggregate對應PizzaMenu和BarbecueMenu

這裡簡單說明一下聚合物件,首先當我們說集合的時候,是指一群物件,其儲存方式可以是各種各樣的資料結構,例如列表、陣列、散列表,有時候集合也會叫做聚合。

還記得我們剛剛討論的嗎,為什麼不直接用PizzaMenu來實現Iterator介面然後實現遍歷集合的這些方法?

當你這麼做之後,就會發現PizzaMenu不僅僅要維護MenuItems這個集合,還要承擔遍歷這個集合的責任,這樣的話,這個集合改變,MenuItem就必須改變,若遍歷的方式改變,這個集合又要改變,這樣它就承擔了兩種變化的風險了。根據這個理念,我們引入了單一變化原則來說明這件事情。

單一變化原則:一個類應該只有一個引起變化的原因。

類的每一個責任都有改變的潛在區域。超過一個責任,意味著超過一個改變的區域。這個原則告訴我們儘量讓每個類保持“單一責任”。

高內聚低耦合是我們經常聽到的,內聚是用來度量一個類或一個模組緊密地達到單一目的或責任。當一個類或模組被設計成只支援一組相關地功能時,我們說它具有高內聚;反之,當被設計成支援一組不相關地功能時,我們說它具有低內聚。內聚是一個比單一責任更加普遍的概念,但兩者關係密切,遵守這原則的類容易具有很高的凝聚力,比低內聚的類容易維護得多。

Vander在歡樂海岸的火鍋店的店租也越來越貴了,近兩個月甚至已經入不敷出了,所以它決定擴大pizza店的店面,加入燒烤之後再加入火鍋,這樣讓客人有更多地選擇,可惜地是廚師C的自助火鍋店的選單又是用另一種資料結構來完成的,不要緊,現在採用的系統已經具有一定的擴充套件性了。接下來新增火鍋店的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,並且還要新增多一句遍歷新選單的語句。我們將其進一步進行改進。

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());
		}
	}
	
}

最後又到了喜聞樂見的總結部分,我們又來總結我們現在現有的設計模式武器。

面向物件基礎

         抽象、封裝、多型、繼承

八大設計原則

設計原則一:封裝變化

設計原則二:針對介面程式設計,不針對實現程式設計。

         設計原則三:多用組合,少用繼承。

         設計原則四:為互動物件之間的鬆耦合設計而努力

設計原則五:對擴充套件開放,對修改關閉

設計原則六:依賴抽象,不要依賴於具體的類

設計原則七:只和你的密友談話

設計原則八:別找我,我有需要會找你

設計原則九:類應該只有一個改變的理由

模式

迭代器模式:提供一個方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的實現。

最後獻上此次設計所涉及到的原始碼,有需要的小夥伴可以下載來執行一下,首先先自己進行設計,然後再參考,這樣才能加深模式的理解。