1. 程式人生 > >設計模式九之迭代器與組合模式(java)

設計模式九之迭代器與組合模式(java)

這是我看Head first設計模式書籍之後想要總結的知識點,一方面是對自己學習的東西總結和提煉加強自己的理解和記憶,另一方面是給大家簡化這本書,方便大家快速瞭解各種設計模式。

我想提醒大家的是,設計模式只是前人總結的一些經驗套路,實際上還是要在開發專案中慢慢體會,不可成為設計模式的中毒患者,強行照搬設計模式的一些規則。

你可以有很多方法把物件堆起來放入一個集合中,可以使用陣列、堆疊、列表或者是散列表等資料結構來儲存物件,
我們將學習如何遍歷這些集合中的物件,又不暴露物件的儲存方式。也將學習如何將多種儲存方式的集合來統一遍歷。
直接給程式碼了,具體的可以看程式碼註釋。

/*
舉個例子:
兩家餐廳物件村餐廳和物件村煎餅屋都有自己儲存選單項物件的資料結構,直接看程式碼
*/

// 選單項類
public class MenuItem{
	String name;
	String description;
	boolean vegetarian;
	double price;

	public MenuItem(String name, String description, 
		boolean vegetarian, double price){
		this.name = name;
		this.description = description;
		this.vegetarian = vegetarian;
		this.price = price;
	}

	public String getName(){
		return name;
	}

	public String getDescription(){
		return description;
	}

	public boolean getVegetarian(){
		return vegetarian;
	}

	public double getPrice(){
		return price;
	}
}

import java.util.ArrayList;


//兩家餐廳的實現方式

public class PancakeHouseMenu{
	ArrayList<MenuItem> menuItems;

	public PancakeHouseMenu(){
		menuItems = new ArrayList<>();

		addItem("K&B's Pancake Breakfast", 
			"Pancakes with scrambled eggs, and toast",
			true,
			2.99);

		addItem("Regular Pancake Breakfast",
			"Pancakes with fried eggs, sausage",
			false,
			2.99);

		addItem("Bleberry Pancakes", 
			"Pancakes made with fresh blueberries",
			true,
			3.49);

		addItem("Waffles", 
			"Waffles, with your choice of blueberries or strawberries",
			true,
			3.59);
	}

	public void addItem(String name, String description,
		boolean vegetarian, double price){
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menuItem);
	}

	public ArrayList<MenuItem> getMenuItems(){
		return menuItems;
	}

	//其他方法依賴於ArrayList儲存方式
}


//另一家餐廳類

public class DinerMenu{
	static final int MAX_ITEMS = 6;
	int numberOfItems = 0;
	//使用一個數組可以控制選單的長度,取出選單項的時候不需要向下轉型
	MenuItem[] menuItems; 

	public DinerMenu(){
		menuItems = new MenuItem[MAX_ITEMS];

		addItem("Vegetarian BLT", 
			"(Fakin') Bacon with lettuce & tomato on whole wheat",
			true,
			2.99);

		addItem("BLT",
			"Bacon with lettuce & tomato on whole wheat",
			false,
			2.99);

		addItem("Soup of the day", 
			"Soup of the day, with a side of potato salad",
			false,
			3.29);

		addItem("Hotdog", 
			"A hot dog, with saurkraut, relish, onions, topped with cheese",
			false,
			3.05);
	}

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

	public MenuItem[] getMenuItems(){
		return menuItems;
	}

	//這裡還有其他的方法依賴陣列的儲存方式
}


//如果要遍歷兩個餐廳提供的所有選單,只能逐個實現,而且依賴於各自集合的儲存資料結構,呼叫者必須知道選單項的儲存方式,不符合封裝的思想

import java.util.ArrayList;

public class PrintMenuTest {
	
	static PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
	static ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();
	
	static DinerMenu dinerMenu = new DinerMenu();
	static MenuItem[] lunchItems = dinerMenu.getMenuItems();
	
	public static void main(String args[]) {
		printPancakeHouseMenu();
		printDinerMenu();
	}
	
	public static void printPancakeHouseMenu() {
		for(int i = 0; i < breakfastItems.size(); i ++) {
			MenuItem menuItem = (MenuItem)breakfastItems.get(i);
			System.out.print(menuItem.getName() + " ");
			System.out.println(menuItem.getPrice() + " ");
			System.out.println(menuItem.getDescription());
		}
	}
	
	public static void printDinerMenu() {
		for(int i = 0; i < lunchItems.length; i ++) {
			MenuItem menuItem = lunchItems[i];
			if(menuItem != null) {
				System.out.print(menuItem.getName() + " ");
				System.out.println(menuItem.getPrice() + " ");
				System.out.println(menuItem.getDescription());
			}
		}
	}
}


//在這裡發生變化的是由不同集合型別所造成的遍歷,我們可以想這些變化封裝起來,我們可以利用迭代器介面生成具體的迭代器來複合不同形式的儲存方式的餐廳類

//迭代器介面
public interface Iterator{
	boolean hasNext(); //返回一個布林值,是否有下一個元素
	Object next(); // 返回下一個元素
}

//為DinerMenu實現Iterator
public class DinerMenuIterator implements Iterator{
	MenuItem[] items;
	int position = 0; //position記錄當前陣列遍歷的位置

	//構造器需要傳入一個選單項的陣列當做引數
	public DinerMenuIterator(MenuItem[] items){ 
		this.items = items;
	}

	//返回陣列中下一項,並遞增其位置
	public Object next(){
		MenuItem menuItem = items[position];
		position = position + 1;
		return menuItem;
	}

	public boolean hasNext(){
		//檢查是否越界以及是否不存在MenuItem物件
		if(position >= items.length || items[position] == null){
			return false;
		} else {
			return true;
		}
	}
}

//用迭代器改寫DinerMenu選單,我們建立一個新的類DinerMenuWithIterator

package com.gougoucompany.designpattern.iteratorandcombination;

//另一家餐廳類

public class DinerMenuWithIterator{
	static final int MAX_ITEMS = 6;
	int numberOfItems = 0;
	//使用一個數組可以控制選單的長度,取出選單項的時候不需要向下轉型
	MenuItem[] menuItems; 

	public DinerMenuWithIterator(){
		menuItems = new MenuItem[MAX_ITEMS];

		addItem("Vegetarian BLT", 
			"(Fakin') Bacon with lettuce & tomato on whole wheat",
			true,
			2.99);

		addItem("BLT",
			"Bacon with lettuce & tomato on whole wheat",
			false,
			2.99);

		addItem("Soup of the day", 
			"Soup of the day, with a side of potato salad",
			false,
			3.29);

		addItem("Hotdog", 
			"A hot dog, with saurkraut, relish, onions, topped with cheese",
			false,
			3.05);
	}

	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;
		}
	}
	
	//呼叫者不需要知道集合的具體儲存方式,只需要知道可以返回對應型別的迭代器物件來遍歷集合就可以了
	public Iterator createIterator() {
		return new DinerMenuIterator(menuItems);
	}

	//不需要這個方法,會暴露我們內部的實現
//	public MenuItem[] getMenuItems(){
//		return menuItems;
//	}

	//這裡還有其他的方法依賴陣列的儲存方式
}

//PancakeHouseMenuIterator迭代器實現
package com.gougoucompany.designpattern.iteratorandcombination;

import java.util.ArrayList;

public class PancakeHouseMenuIterator implements Iterator{
	ArrayList<MenuItem> menuItems;
	int position = 0; //記錄遍歷的位置
	
	public PancakeHouseMenuIterator(ArrayList<MenuItem> menuItems) {
		this.menuItems = menuItems;
	}

	@Override
	public boolean hasNext() {
		// 這裡其實不用判斷元素是否是MenuIteme類向上轉型的物件,因為泛型做出了限制
		if(position >= menuItems.size() || !(menuItems.get(position) instanceof MenuItem)) {
			return false;
		} else {
			return true;
		}
	}

	@Override
	public Object next() {
		MenuItem menuItem = menuItems.get(position);
		position += 1;
		return menuItem;
	}

}

//加入生成迭代器方法的類
package com.gougoucompany.designpattern.iteratorandcombination;

import java.util.ArrayList;

//兩家餐廳的實現方式

public class PancakeHouseMenuWithIterator{
	ArrayList<MenuItem> menuItems;

	public PancakeHouseMenuWithIterator(){
		menuItems = new ArrayList<>();

		addItem("K&B's Pancake Breakfast", 
			"Pancakes with scrambled eggs, and toast",
			true,
			2.99);

		addItem("Regular Pancake Breakfast",
			"Pancakes with fried eggs, sausage",
			false,
			2.99);

		addItem("BlueBerry Pancakes", 
			"Pancakes made with fresh blueberries",
			true,
			3.49);

		addItem("Waffles", 
			"Waffles, with your choice of blueberries or strawberries",
			true,
			3.59);
	}

	public void addItem(String name, String description,
		boolean vegetarian, double price){
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menuItem);
	}

//	public ArrayList<MenuItem> getMenuItems(){
//		return menuItems;
//	}
	
	public Iterator createIterator() {
		return new PancakeHouseMenuIterator(menuItems);
	}

	//其他方法依賴於ArrayList儲存方式
}


//我們的服務員拿到選單不需要知道選單的儲存方式可以通過拿到選單迭代器來遍歷集合中的物件
package com.gougoucompany.designpattern.iteratorandcombination;


public class Waitress{
	PancakeHouseMenuWithIterator pancakeHouseMenu;
	DinerMenuWithIterator dinerMenu;

	public Waitress(PancakeHouseMenuWithIterator pancakeHouseMenu, DinerMenuWithIterator dinerMenu){
		this.pancakeHouseMenu = pancakeHouseMenu;
		this.dinerMenu = dinerMenu;
	}

	public void printMenu(){
		Iterator pancakeIterator = pancakeHouseMenu.createIterator();
		Iterator dinerMenuIterator = dinerMenu.createIterator();
		System.out.println("MENU\n----------\nBREAKFAST");
		printMenu(pancakeIterator);
		System.out.println("\nLUNCH");
		printMenu(dinerMenuIterator);
	}

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

//測試程式
package com.gougoucompany.designpattern.iteratorandcombination;
public class MenuTestDrive{
	public static void main(String args[]){
		PancakeHouseMenuWithIterator pancakeHouseMenu = new PancakeHouseMenuWithIterator();
		DinerMenuWithIterator dinerMenu = new DinerMenuWithIterator();
		
//		Object obj = (Object)dinerMenu;
//		
//		System.out.println(dinerMenu instanceof DinerMenuWithIterator);
//		System.out.println(obj instanceof DinerMenuWithIterator);
//		
//		System.out.println(DinerMenuWithIterator.class.isInstance(dinerMenu));
//		System.out.println(DinerMenuWithIterator.class.isInstance(obj));

		Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);

		waitress.printMenu();
	}
}

//MENU
//----------
//BREAKFAST
//K&B's Pancake Breakfast, 2.99 -- Pancakes with scrambled eggs, and toast
//Regular Pancake Breakfast, 2.99 -- Pancakes with fried eggs, sausage
//BlueBerry Pancakes, 3.49 -- Pancakes made with fresh blueberries
//Waffles, 3.59 -- Waffles, with your choice of blueberries or strawberries
//
//LUNCH
//Vegetarian BLT, 2.99 -- (Fakin') Bacon with lettuce & tomato on whole wheat
//BLT, 2.99 -- Bacon with lettuce & tomato on whole wheat
//Soup of the day, 3.29 -- Soup of the day, with a side of potato salad
//Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese




//上面的迭代器方法可以讓Waitress女招待從具體實現中解耦,不需要知道選單項的具體實現方式,
//不同的選單都實現createIterator()方法來建立屬於自己的迭代器物件,但是並沒有實現相同的介面
//我們將修改這一點,讓女招待不依賴具體的選單
//我們將使用java.util.Iterator自帶的迭代器介面
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//為DinerMenu實現Iterator
public class DinerMenuIterator implements Iterator<MenuItem>{
	MenuItem[] items;
	int position = 0; //position記錄當前陣列遍歷的位置

	//構造器需要傳入一個選單項的陣列當做引數
	public DinerMenuIterator(MenuItem[] items){ 
		this.items = items;
	}

	@Override
	public boolean hasNext() {
		//檢查是否越界以及是否不存在MenuItem物件
				if(position >= items.length || items[position] == null){
					return false;
				} else {
					return true;
				}
	}

	@Override
	public MenuItem next() {
		MenuItem menuItem = items[position];
		position = position + 1;
		return menuItem;
	}
	
	/**
	 * remove允許我們從聚合中刪除由next方法返回的最後一項
	 */
	@Override
	public void remove() {
		//如果不需要remove()方法,可以丟擲java.lang.UnsupportedOperationException執行時異常
		//RuntimeException執行時異常是不需要捕獲的,當異常出現時,虛擬機器會處理,常見的有空指標異常
		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) {
			//覆蓋掉position位置的元素
			for(int i = position -1; i < (items.length - 1); i++) {
				items[i] = items[i + 1];
			}
			items[items.length - 1] = null; //刪除元素前移後要覆蓋最後一項元素,防止重合
		}
	}
}



//兩家餐廳的實現方式

package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//另一家餐廳類

public class DinerMenuWithIterator implements Menu{
	static final int MAX_ITEMS = 6;
	int numberOfItems = 0;
	//使用一個數組可以控制選單的長度,取出選單項的時候不需要向下轉型
	MenuItem[] menuItems; 

	public DinerMenuWithIterator(){
		menuItems = new MenuItem[MAX_ITEMS];

		addItem("Vegetarian BLT", 
			"(Fakin') Bacon with lettuce & tomato on whole wheat",
			true,
			2.99);

		addItem("BLT",
			"Bacon with lettuce & tomato on whole wheat",
			false,
			2.99);

		addItem("Soup of the day", 
			"Soup of the day, with a side of potato salad",
			false,
			3.29);

		addItem("Hotdog", 
			"A hot dog, with saurkraut, relish, onions, topped with cheese",
			false,
			3.05);
	}

	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;
		}
	}
	
	//呼叫者不需要知道集合的具體儲存方式,只需要知道可以返回對應型別的迭代器物件來遍歷集合就可以了
	public Iterator<MenuItem> createIterator() {
		return new DinerMenuIterator(menuItems);
	}

	//不需要這個方法,會暴露我們內部的實現
//	public MenuItem[] getMenuItems(){
//		return menuItems;
//	}

	//這裡還有其他的方法依賴陣列的儲存方式
}



package com.gougoucompany.designpattern.iteratorsecond;

import java.util.ArrayList;
import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//兩家餐廳的實現方式

public class PancakeHouseMenuWithIterator implements Menu{
	ArrayList<MenuItem> menuItems;

	public PancakeHouseMenuWithIterator(){
		menuItems = new ArrayList<>();

		addItem("K&B's Pancake Breakfast", 
			"Pancakes with scrambled eggs, and toast",
			true,
			2.99);

		addItem("Regular Pancake Breakfast",
			"Pancakes with fried eggs, sausage",
			false,
			2.99);

		addItem("BlueBerry Pancakes", 
			"Pancakes made with fresh blueberries",
			true,
			3.49);

		addItem("Waffles", 
			"Waffles, with your choice of blueberries or strawberries",
			true,
			3.59);
	}

	public void addItem(String name, String description,
		boolean vegetarian, double price){
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menuItem);
	}

//	public ArrayList<MenuItem> getMenuItems(){
//		return menuItems;
//	}
	
	public Iterator<MenuItem> createIterator() {
		return menuItems.iterator(); //不建立自己的迭代器,而呼叫選單項ArrayList的iterator方法
	}

	//其他方法依賴於ArrayList儲存方式
}


//提供一個公共的選單介面,不同的選單可以呼叫返回他們的迭代器
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Iterator;
import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//給選單一個共同的幾口,讓呼叫者取得一個選單項的迭代器
public interface Menu {
	public Iterator<MenuItem> createIterator();
}


//女招待類 不依賴於具體的選單類,因為使用了Menu介面,可以不論什麼選單,只需要呼叫介面的方法返回迭代器遍歷就可以了
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//我們的服務員拿到選單不需要知道選單的儲存方式可以通過拿到選單迭代器來遍歷集合中的物件
public class Waitress{
	Menu pancakeHouseMenu;
	Menu dinerMenu;

	//將具體選單類改成介面
	public Waitress(Menu pancakeHouseMenu, Menu dinerMenu){
		this.pancakeHouseMenu = pancakeHouseMenu;
		this.dinerMenu = dinerMenu;
	}

	public void printMenu(){
		Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
		Iterator<MenuItem> dinerMenuIterator = dinerMenu.createIterator();
		System.out.println("MENU\n----------\nBREAKFAST");
		printMenu(pancakeIterator);
		System.out.println("\nLUNCH");
		printMenu(dinerMenuIterator);
	}

	private void printMenu(Iterator<MenuItem> iterator){
		while(iterator.hasNext()){
			MenuItem menuItem = (MenuItem) iterator.next(); //注意這裡要向下轉型 
			System.out.print(menuItem.getName() + ", ");
			System.out.print(menuItem.getPrice() + " -- ");
			System.out.println(menuItem.getDescription());
		}
	}
}



//測試類
package com.gougoucompany.designpattern.iteratorsecond;

//測試程式
public class MenuTestDrive{
	public static void main(String args[]){
		PancakeHouseMenuWithIterator pancakeHouseMenu = new PancakeHouseMenuWithIterator();
		DinerMenuWithIterator dinerMenu = new DinerMenuWithIterator();
		
//		Object obj = (Object)dinerMenu;
//		
//		System.out.println(dinerMenu instanceof DinerMenuWithIterator);
//		System.out.println(obj instanceof DinerMenuWithIterator);
//		
//		System.out.println(DinerMenuWithIterator.class.isInstance(dinerMenu));
//		System.out.println(DinerMenuWithIterator.class.isInstance(obj));

		Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);

		waitress.printMenu();
	}
}

/*
MENU
----------
BREAKFAST
K&B's Pancake Breakfast, 2.99 -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99 -- Pancakes with fried eggs, sausage
BlueBerry Pancakes, 3.49 -- Pancakes made with fresh blueberries
Waffles, 3.59 -- Waffles, with your choice of blueberries or strawberries

LUNCH
Vegetarian BLT, 2.99 -- (Fakin') Bacon with lettuce & tomato on whole wheat
BLT, 2.99 -- Bacon with lettuce & tomato on whole wheat
Soup of the day, 3.29 -- Soup of the day, with a side of potato salad
Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese
*/
/*
這樣程式設計的好處,兩個選單都實現了Menu介面,女招待可以利用介面,而不是具體類引用每一個
選單物件,通過"針對介面程式設計,而不是針對實現程式設計"可以減少女招待和具體類之間的依賴。
定義迭代器模式:
	迭代器模式提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的表示。
迭代器模式把元素之間的遊走的責任交給迭代器,而不是聚合物件。這不僅讓聚合的介面和實現
變得更加簡潔,也可以讓聚合更專注在它所應該專注的事情上面,而不必去理會遍歷的事情。

單一責任:
	一個類應該只有一個引起變化的原因,比如選單類,實現管理某種集合,把遍歷的責任交給迭代器去完成,
	這樣該類就不容易錯誤,也容易修改。
內聚: 它用來度量一個類或模組緊密地達到單一目的或責任。 當一個模組或一個類被設計成只支援一組相關的功能時,
我們說它具有高內聚。內聚是一個比單一責任更普遍的概念,但兩者關係密切。高內聚的類更容易維護。
*/



//我們又有新的需求,需要合併咖啡廳的選單
//咖啡廳的選單
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Hashtable;
import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//我們在原來的早餐和午餐選單中加入了晚餐選單,這時候迭代器的優點就可以體現出來了
public class CafeMenu {
	Hashtable<String, MenuItem> menuItems = new Hashtable<String, MenuItem>();
	
	public CafeMenu() {
	
	}
	
	public void addItem(String name, String description,
			boolean vegetarian, double price) {
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.put(menuItem.getName(), menuItem);
	}
	
	public Hashtable<String, MenuItem> getItems() {
		return menuItems;
	}
	
}
//重做咖啡廳程式碼

package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Hashtable;
import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//我們在原來的早餐和午餐選單中加入了晚餐選單,這時候迭代器的優點就可以體現出來了
public class CafeMenu implements Menu{
	Hashtable<String, MenuItem> menuItems = new Hashtable<String, MenuItem>();
	
	public CafeMenu() {
		addItem("Veggie Burger and Air Fries",
				"Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
				true, 3.99);
		addItem("Soup of the day",
				"A cup of the soup of the day, with a side salad",
				false, 3.69);
		addItem("Burrito",
				"A large burrito, with whole pinto beans, salsa, guacamole",
				true, 4.29);
	}
	
	public void addItem(String name, String description,
			boolean vegetarian, double price) {
		MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
		menuItems.put(menuItem.getName(), menuItem);
	}
	
	public Hashtable<String, MenuItem> getItems() {
		return menuItems;
	}

	@Override
	public Iterator<MenuItem> createIterator() {
		// TODO Auto-generated method stub
		return menuItems.values().iterator(); //取得值的那部分迭代器
	}
	
}

//修改女招待類
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

//我們的服務員拿到選單不需要知道選單的儲存方式可以通過拿到選單迭代器來遍歷集合中的物件
public class Waitress{
	Menu pancakeHouseMenu;
	Menu dinerMenu;
	Menu cafeMenu;

	//將具體選單類改成介面
	public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu){
		this.pancakeHouseMenu = pancakeHouseMenu;
		this.dinerMenu = dinerMenu;
		this.cafeMenu = cafeMenu;
	}

	public void printMenu(){
		Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
		Iterator<MenuItem> dinerMenuIterator = dinerMenu.createIterator();
		Iterator<MenuItem> cafeMenuIterator = cafeMenu.createIterator();
		System.out.println("MENU\n----------\nBREAKFAST");
		printMenu(pancakeIterator);
		System.out.println("\nLUNCH");
		printMenu(dinerMenuIterator);
		System.out.println("\nDINNER");
		printMenu(cafeMenuIterator);
	}

	private void printMenu(Iterator<MenuItem> iterator){
		while(iterator.hasNext()){
			MenuItem menuItem = (MenuItem) iterator.next(); //注意這裡要向下轉型 
			System.out.print(menuItem.getName() + ", ");
			System.out.print(menuItem.getPrice() + " -- ");
			System.out.println(menuItem.getDescription());
		}
	}
}
//測試類

package com.gougoucompany.designpattern.iteratorsecond;

//測試程式
public class MenuTestDrive{
	public static void main(String args[]){
		PancakeHouseMenuWithIterator pancakeHouseMenu = new PancakeHouseMenuWithIterator();
		DinerMenuWithIterator dinerMenu = new DinerMenuWithIterator();
		CafeMenu cafeMenu = new CafeMenu();
		
//		Object obj = (Object)dinerMenu;
//		
//		System.out.println(dinerMenu instanceof DinerMenuWithIterator);
//		System.out.println(obj instanceof DinerMenuWithIterator);
//		
//		System.out.println(DinerMenuWithIterator.class.isInstance(dinerMenu));
//		System.out.println(DinerMenuWithIterator.class.isInstance(obj));

		Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);

		waitress.printMenu();
	}
}

/*
Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese

MENU
----------
BREAKFAST
K&B's Pancake Breakfast, 2.99 -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99 -- Pancakes with fried eggs, sausage
BlueBerry Pancakes, 3.49 -- Pancakes made with fresh blueberries
Waffles, 3.59 -- Waffles, with your choice of blueberries or strawberries

LUNCH
Vegetarian BLT, 2.99 -- (Fakin') Bacon with lettuce & tomato on whole wheat
BLT, 2.99 -- Bacon with lettuce & tomato on whole wheat
Soup of the day, 3.29 -- Soup of the day, with a side of potato salad
Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese

DINNER
Soup of the day, 3.69 -- A cup of the soup of the day, with a side salad
Burrito, 4.29 -- A large burrito, with whole pinto beans, salsa, guacamole
Veggie Burger and Air Fries, 3.99 -- Veggie burger on a whole wheat bun, lettuce, tomato, and fries

*/



/*
Java5中,所有的集合都已經新增了對遍歷的支援,所以你甚至不需要請求迭代器了
for/in這可以讓你在一個集合或者一個數組中遍歷,而且不需要顯示建立迭代器。
for(Object obj : collection){
	....
}

每次我們有新的選單加入,就必須開啟女招待實現並加入更多的程式碼,而且print方法程式碼時重複的。
我們仍然將選單成分離而獨立的物件--我們需要一種一起管理他們的方法。
*/
package com.gougoucompany.designpattern.iteratorsecond;

import java.util.ArrayList;
import java.util.Iterator;

import com.gougoucompany.designpattern.iteratorfirst.MenuItem;

public class WaitressSecond {
	ArrayList<Menu> menus;
	
	public WaitressSecond(ArrayList<Menu> menus) {
		this.menus = menus;
	}
	
	public void printMenu() {
//		Iterator<MenuItem> menuIterator = menus.iterator(); 
//		while(menuIterator.hasNext()) {
//			Menu menu = (Menu)menuIterator.next();
//			printMenu(menu.createIterator());
//		}
		
		for(Menu menu : menus) {
			printMenu(menu.createIterator());
		}
	}
	
	private void printMenu(Iterator<MenuItem> iterator) {
		while(iterator.hasNext()) {
			MenuItem menuItem = (MenuItem) iterator.next(); //注意這裡要向下轉型 
			System.out.print(menuItem.getName() + ", ");
			System.out.print(menuItem.getPrice() + " -- ");
			System.out.println(menuItem.getDescription());
		}
	}
}



/*
需求又變更了,我們希望在餐廳選單中加入一個甜點選單,但是我們不能直接將甜點選單賦值給選單項陣列,
型別不同,又要修改了。實際上,我們已經到達了一個複雜級別,如果現在不重新設計,就無法容納未來增加
的選單或子選單等需求。

我們要介紹另一個模式解決這個難題-->組合模式
組合模式:
	允許你將物件組合成樹形結構來表現"整體/部分"層次結構。組合能讓客戶以一致的方式處理個別對象以及物件組合。
也就是說我們能把相同的操作應用在組合和個別物件上。
*/



/*
利用組合設計選單
我們需要建立一個元件介面來作為選單和選單項的共同介面,讓我們能夠用統一的做法來處理選單和選單項。 
類組成:
Waitress女招待類: 將使用選單元件介面訪問選單和選單項
MenuComponent介面類: 選單元件提供了一個介面,讓選單項和選單共同使用。我們希望能為他們提供預設的實現,
所以在這裡定義成抽象類。
MenuComponent方法: getName(), getDescription(), getPrice(), isVegetarian(), print(), add(Component), remove(Component), getChild(int)
選單項類MenuItem: getName(), getDescription(), getPrice(), isVegetarian(), print()
Menu類: 屬性->menuComponents 方法->getName(), getDescription(), print(), add(Component), remove(Component), getChild(int)
*/

//實現選單元件
/*
MenuComponent對每個方法都提供預設的實現,因為有些方法只對選單項有意義,而有些只對選單有意義,預設實現是丟擲UnsupportedOperationException
異常。這樣,如果選單項或選單不支援某個操作,他們就不需做任何事情,直接繼承預設實現就可以了
*/
package com.gougoucompany.designpattern.iteratorthird;

public abstract class MenuComponent {
	
	public void add(MenuComponent menuComponent) {
		throw new UnsupportedOperationException();
	}
	
	public void remove(MenuComponent menuComponent) {
		throw new UnsupportedOperationException();
	}
	
	public MenuComponent getChild(int i) {
		throw new UnsupportedOperationException();
	}
	
	public String getName() {
		throw new UnsupportedOperationException();
	}
	
	public String getDescription() {
		throw new UnsupportedOperationException();
	}
	
	public double getPrice() {
		throw new UnsupportedOperationException();
	}
	
	public boolean isVegetarian() {
		throw new UnsupportedOperationException();
	}
	
	public void print() {
		throw new UnsupportedOperationException();
	}
}

//選單項類,這是組合類圖裡的葉子節點,它實現組合內元素的行為

package com.gougoucompany.designpattern.iteratorthird;
public class MenuItem extends MenuComponent{
	String name;
	String description;
	boolean vegetarian;
	double price;
	
	public MenuItem(String name, String description,
			boolean vegetarian, double price) {
		this.name = name;
		this.description = description;
		this.vegetarian = vegetarian;
		this.price = price;
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}

	public boolean isVegetarian() {
		return vegetarian;
	}

	public double getPrice() {
		return price;
	}
	
	public void print() {
		System.out.print("  " +  getName());
		if(isVegetarian()) {
			System.out.print("(v)");
		}
		System.out.println(", " + getPrice());
		System.out.println("   -- " + getDescription());
	}
}

//實現組合菜單,此組合菜單可以持有選單項或其他選單

package com.gougoucompany.designpattern.iteratorthird;

import java.util.ArrayList;

public class Menu extends MenuComponent {
	ArrayList<MenuComponent> menuComponents = new ArrayList<>();
	String name;
	String description;
	
	public Menu(String name, String description) {
		this.name = name;
		this.description = description;
	}
	
	public void add(MenuComponent menuComponent) {
		menuComponents.add(menuComponent);
	}
	
	public void remove(MenuComponent menuComponent) {
		menuComponents.remove(menuComponent);
	}
	
	public MenuComponent getChild(int i) {
		return (MenuComponent)menuComponents.get(i);
	}
	
	//注: 我們沒有覆蓋getPrice()和getVegetarian(),因為這些方法可能對Menu沒有意義,如果呼叫這些方法,會丟擲UnsupportedOperationException異常
	public String getName() {
		return name;
	}
	
	public String getDescription() {
		return description;
	}
	
	//列印選單的名稱和描述
	public void print() {
		System.out.print("\n" + getName());
		System.out.println(", " + getDescription());
		System.out.println("-----------------------");
	}
}

//我們Menu類print()方法必須打印出它包含的一切,可以使用遞迴的方式來完成。我們修正一下這個print()
public void print() {
		System.out.print("\n" + getName());
		System.out.println(", " + getDescription());
		System.out.println("-----------------------");
		
		Iterator<MenuComponent> iterator = menuComponents.iterator();
		while(iterator.hasNext()) {
			MenuComponent menuComponent = (MenuComponent)iterator.next();
			menuComponent.print(); //如果碰到選單項直接打印出選單項資訊,如果不是打印出選單資訊並且繼續遞迴直到葉節點
		}
	}


//Waitress類
package com.gougoucompany.designpattern.iteratorthird;

public class Waitress {
	MenuComponent allMenus;
	
	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}
	
	public void printMenu() {
		allMenus.print(); //只需呼叫最頂層選單的print()
	}
}


//測試程式
package com.gougoucompany.designpattern.iteratorthird;

public class MenuTestDrive {
	public static void main(String[] args) {
		MenuComponent pancakeHouseMenu =
				new Menu("PANCAKE HOUSE MENU", "Breakfast");
		MenuComponent dinerMenu =
				new Menu("DINER MENU", "Lunch");
		MenuComponent cafeMenu =
				new Menu("CAFE MENU", "Diner");
		MenuComponent dessertMenu =
				new Menu("DESSERT MENU", "Dessert of course!");
		
		MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
		
		allMenus.add(pancakeHouseMenu);
		allMenus.add(dinerMenu);
		allMenus.add(cafeMenu);
		
		//這裡加入其它選單項
		
		dinerMenu.add(new MenuItem(
				"Pasta", 
				"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
				true,
				3.89));
		
		dinerMenu.add(dessertMenu);
		
		dessertMenu.add(new MenuItem(
				"Apple Pie", 
				"Apple pie with a flakey crust, topped with vanilla ice cream",
				true,
				1.59));
		
		//在這裡加入更多選單項
		
		Waitress waitress = new Waitress(allMenus);
		waitress.printMenu();	
		
	}
}


/*
從結果來分析,我們根節點下面有三個選單結點,只有dinerMenu有一個選單項,和一個選單,另外兩個選單項沒有子節點
遞迴順序就是前序遍歷的順序,先是pancakeHouseMenu,dinerMenu,子選單Pasta,dessertMenu,選單項Apple pie,,最後是cafeMenu
ALL MENUS, All menus combined
-----------------------

PANCAKE HOUSE MENU, Breakfast
-----------------------

DINER MENU, Lunch
-----------------------
  Pasta(v), 3.89
   -- Spaghetti with Marinara Sauce, and a slice of sourdough bread

DESSERT MENU, Dessert of course!
-----------------------
  Apple Pie(v), 1.59
   -- Apple pie with a flakey crust, topped with vanilla ice cream

CAFE MENU, Diner
-----------------------
 */

/*
組合模式不但要管理層次結構,而且要執行選單的操作,具有兩個責任。組合模式
以單一責任設計原則換取透明性。
透明性: 通過讓元件的介面同時包含一些管理子節點和葉節點的操作,客戶就可以將組合
和葉節點一視同仁,也就是說,一個元素究竟是組合還是葉節點,對客戶是透明的。
MenuComponent類中同時具有兩種型別的操作,因為客戶有機會對一個元素做出一些不恰當或者是
沒有意義的操作(例如試圖將選單加入選單項),所以失去了一些"安全性".
我們可以採取另一種方式,將責任區分開來放在不同的介面中,這麼一來,設計上就比較安全,但我們也
因此失去了透明性,客戶的程式碼將必須用條件語句和instanceof操作符處理不同型別的結點。
*/

//現在我們設計自己迭代器來滿足一些特定的需求,例如女招待可能想要遊走整個選單,調出素食項。
// 想要實現一個組合迭代器,讓我們為每個元件都加上createIterator()方法,每個選單和選單項都必須實現這個方法。在MenuComponent中加入一個createIterator抽象方法
//在MenuComponent加入此抽象方法public abstract Iterator createIterator();並在選單和選單項中實現這個方法

//CompositeIterator迭代器責任是遍歷元件內的選單項,而且確保所有的子選單都被包括進來。
//CompositeIterator類實現
package com.gougoucompany.designpattern.iteratorthird;

import java.util.Iterator;
import java.util.Stack;

public class CompositeIterator implements Iterator<MenuComponent>{
	Stack<Iterator<MenuComponent>> Stack = new Stack<>();
	
	public CompositeIterator(Iterator<MenuComponent> iterator) {
		Stack.push(iterator); //將要遍歷的頂層組合的迭代器傳入,壓入堆疊
	}

	@Override
	public boolean hasNext() {
		//是否有下一個元素,如果堆疊為空,沒有下一個元素
		if(Stack.empty()) {
			return false;
		} else {
			//否則, 從棧頂取出迭代器,如果它沒有下一個元素,則彈出堆疊,遞迴呼叫haxNext()
			Iterator<MenuComponent> iterator = (Iterator<MenuComponent>) Stack.peek(); //檢視堆疊中第一個首元素
			if(!iterator.hasNext()) {
				Stack.pop();
				return hasNext();
			} else {
				return true; //否則表示還有下一個元素,返回true
			}
		}
	}

	@Override
	public MenuComponent next() {
		//客戶要取下一個元素的時候,先用hasNext()確認是否還有下一個
		if(hasNext()) {
			Iterator<MenuComponent> iterator = (Iterator<MenuComponent>) Stack.peek();
			MenuComponent component = (MenuComponent) iterator.next();
			if (component instanceof Menu) {
				//如果元素是一個選單,則我們有了另一個需要被包含進遍歷的組合,所有的子集合的迭代器,壓入堆疊。
				Stack.push(component.createIterator()); //這種類似於深度優先遍歷
			}
			return (MenuComponent) component;
		} else {
			return null;
		}
		
	}
	
	public void remove() {
		throw new UnsupportedOperationException(); //不支援刪除,只遍歷
	}

}

/*
空迭代器,選單項內沒有什麼可以遍歷的,因此我們的createIterator()方法返回一個空的迭代器
如何實現?
方法一:
	返回null,但是客戶程式碼就需要用條件語句來判斷返回值是否為null,反之空指標異常,比如呼叫選單項的方法isVegetarian(),首先要判斷是不是null
方法二:
	返回一個迭代器,而這個迭代器的hasNext()永遠返回false
*/
package com.gougoucompany.designpattern.iteratorthird;

import java.util.Iterator;

public class NullIterator implements Iterator<MenuComponent>{

	@Override
	public boolean hasNext() {
		// TODO Auto-generated method stub
		return false;
	}

	
	//空迭代器不支援remove
	public void remove() {
		throw new UnsupportedOperationException();
	}


	@Override
	public MenuComponent next() {
		// TODO Auto-generated method stub
		return null;
	}
}

//使用組合與迭代器之後的Waitress
public class Waitress{
	MenuComponent allMenus;

	public Waitress(MenuComponent allMenus){
		this.allMenus = allMenus;
	}

	public void printMenu(){
		allMenus.print();
	}

	public void printVegetarainMenu(){
		//只打印蔬菜的選單資訊
		Iterator<MenuComponent> iterator = allMenus.createIterator();
		System.out.println("\nVEGETARIAN MENU\n---------")
		while(iterator.hasNext()){
			MenuComponent menuComponent = 
			(MenuComponent) iterator.next();
			//只有選單項可以呼叫isVegetarain()方法,選單會報出異常我們捕獲讓遍歷繼續進行
			try{
				if(menuComponent.isVegetarian()){
					menuComponent.print();
				}
			} catch(UnsupportedOperationException e){}
		}
	}
}



//結果
package com.gougoucompany.designpattern.iteratorthird;

public class MenuTestDrive {
	public static void main(String[] args) {
		MenuComponent pancakeHouseMenu =
				new Menu("PANCAKE HOUSE MENU", "Breakfast");
		MenuComponent dinerMenu =
				new Menu("DINER MENU", "Lunch");
		MenuComponent cafeMenu =
				new Menu("CAFE MENU", "Diner");
		MenuComponent dessertMenu =
				new Menu("DESSERT MENU", "Dessert of course!");
		
		MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
		
		allMenus.add(pancakeHouseMenu);
		allMenus.add(dinerMenu);
		allMenus.add(cafeMenu);
		
		//這裡加入其它選單項
		
		dinerMenu.add(new MenuItem(
				"Pasta", 
				"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
				true,
				3.89));
		
		dinerMenu.add(dessertMenu);
		
		dessertMenu.add(new MenuItem(
				"Apple Pie", 
				"Apple pie with a flakey crust, topped with vanilla ice cream",
				true,
				1.59));
		
		//在這裡加入更多選單項
		
		Waitress waitress = new Waitress(allMenus);
		waitress.printMenu();	
		
	}
}


/*
\
ALL MENUS, All menus combined
-----------------------

PANCAKE HOUSE MENU, Breakfast
-----------------------

DINER MENU, Lunch
-----------------------
  Pasta(v), 3.89
   -- Spaghetti with Marinara Sauce, and a slice of sourdough bread

DESSERT MENU, Dessert of course!
-----------------------
  Apple Pie(v), 1.59
   -- Apple pie with a flakey crust, topped with vanilla ice cream

CAFE MENU, Diner
-----------------------

*/