HeadFirst設計模式(十) - 迭代器模式

良好的管理集合

有許多種方法能夠把對象堆起來成爲一個集合。你能夠把它們放進數據、堆棧、列表或者是散列表中,每一種都有它本身的優勢和適合的使用時機。java

舉個例子

加入如今有兩個餐廳,披薩餐廳和煎餅餐廳,它們合併了,他們的菜單就須要合併在一塊兒。數組

披薩餐廳是用ArrayList對象去存放餐單信息,而煎餅餐廳使用數組。他們兩家餐廳都贊成讓菜單對象實現MenuItem。讓咱們看看具體的實現:數據結構

package cn.net.bysoft.iterator;

// 菜單對象,保存了菜單的信息
public class MenuItem {

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

	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 boolean isVegetarian() {
		return vegetarian;
	}

	public void setVegetarian(boolean vegetarian) {
		this.vegetarian = vegetarian;
	}

	public double getPrice() {
		return price;
	}

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

	private String name;
	private String description;
	private boolean vegetarian;
	private double price;
}
import java.util.ArrayList;

// 煎餅餐廳對象,用ArrayList保存了菜單。
public class PancakeHouseMenu {
	ArrayList<MenuItem> menuItems;
	
	public PancakeHouseMenu() {
		menuItems = new ArrayList<MenuItem>();
		addItem("煎餅1號", "牛肉煎餅", false, 2.99);
		addItem("煎餅2號", "素食煎餅", true, 1.49);
	}
	
	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menu);
	}

	public ArrayList<MenuItem> getMenuItems() {
		return menuItems;
	}
}
// 披薩餐廳對象,用數據保存了菜單信息。
public class PizzaHouseMenu {
	static final int MAX_ITEMS = 2;
	int numberOfItems = 0;
	MenuItem[] menuItems;

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

	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		if (numberOfItems >= MAX_ITEMS)
			System.out.println("對不起,菜單數量已滿");
		else
			menuItems[numberOfItems++] = menu;
	}

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

    想了解爲何有兩種不一樣的菜單表現方式會讓事情變得複雜化,讓咱們試着實現一個同事使用者兩個菜單的客戶代碼。建立一個服務員對象。this

  • printMenu(); 打印出菜單上的每一項
  • printBreakfastMenu(); 只打印早餐
  • printLunchMenu(); 只打印午飯
  • printVegetarianMenu(); 打印全部的素食菜單
  • isItemVegetarian(name); 查詢指定的菜品是不是素食

    先從實現printMenu()方法開始:spa

import java.util.ArrayList;

public class Client {
	public static void main(String[] args) {
		// 先得到煎餅餐廳的菜單集合
		PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
		ArrayList<MenuItem> menusOfPancake = pancakeHouseMenu.getMenuItems();

		// 在得到披薩餐廳的菜單數組
		PizzaHouseMenu pizzaHouseMenu = new PizzaHouseMenu();
		MenuItem[] menusOfPizza = pizzaHouseMenu.getMenuItems();

		for (int i = 0; i < menusOfPancake.size(); i++) {
			MenuItem menu = menusOfPancake.get(i);
			System.out.print(menu.getName() + ",價格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}

		System.out.println();

		for (int i = 0; i < menusOfPizza.length; i++) {
			MenuItem menu = menusOfPizza[i];
			System.out.print(menu.getName() + ",價格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}
	}
}

    打印每份菜單上的全部項,必須調用getMenuItem()方法,來取得他們各自的菜單。上面的例子中二者的返回類型是不一樣的。.net

    接着,想打印出菜單須要將集合和數組循環並一一列出菜單。設計

    最後,咱們老是須要處理這兩個菜單的遍歷,若是還有第三家餐廳以不一樣的方式實現菜單集合,咱們就須要有第三個循環。code

能夠封裝遍歷嗎?

    能夠封裝變化的部分。很明顯,這裏發生的變化是:由不一樣的集合類型所形成的遍歷。可是,這可以被封裝嗎?讓咱們來看看這個想法……對象

  1. 要便利煎餅餐廳,咱們須要使用ArrayList的size()和get()方法;
  2. 要便利披薩餐廳,咱們須要使用數組的length字段和在中括號中輸入索引;
  3. 如今咱們建立一個對象,將它稱爲迭代器(Iterator),利用它來封裝「遍歷集合內的每一個對象的過程」;

     想要在餐廳菜單中加入一個迭代器,咱們須要先定義迭代器接口,而後爲披薩餐廳建立一個迭代器類:索引

public interface Iterator {
	boolean hasNext();
	Object next();
}
public class PizzaIterator implements Iterator {

	MenuItem[] items;
	int position = 0;
	
	public PizzaIterator(MenuItem[] items) {
		this.items = items;
	}
	
	// 判斷數組下一個索引是否還有元素
	public boolean hasNext() {
		if(position >= items.length || items[position] == null)
			return false;
		else return true;
	}
	
	// 得到當前索引位置的元素
	public Object next() {
		MenuItem item = items[position++];
		return item;
	}

}

import java.util.ArrayList;

public class PancakeIterator implements Iterator {

	ArrayList<MenuItem> items;
	int position = 0;
	
	public PancakeIterator(ArrayList<MenuItem> items) {
		this.items = items;
	}
	
	// 判斷數組下一個索引是否還有元素
	public boolean hasNext() {
		if(position >= items.size() || items.get(position) == null)
			return false;
		else return true;
	}
	
	// 得到當前索引位置的元素
	public Object next() {
		MenuItem item = items.get(position);
		return item;
	}

}

    建立好迭代器後,改寫披薩餐廳的代碼,建立一個PizzaMenuIterator,並返回給客戶:

public class PizzaHouseMenu {
	static final int MAX_ITEMS = 2;
	int numberOfItems = 0;
	MenuItem[] menuItems;

	public PizzaHouseMenu() {
		menuItems = new MenuItem[MAX_ITEMS];
		addItem("披薩1號", "素食披薩", true, 4.99);
		addItem("披薩2號", "海鮮蛤蜊披薩", true, 5.99);
	}

	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		if (numberOfItems >= MAX_ITEMS)
			System.out.println("對不起,菜單數量已滿");
		else
			menuItems[numberOfItems++] = menu;
	}
	
	public Iterator createIterator() {
		return new PizzaIterator(menuItems);
	}
}

import java.util.ArrayList;

public class PancakeHouseMenu {
	ArrayList<MenuItem> menuItems;
	
	public PancakeHouseMenu() {
		menuItems = new ArrayList<MenuItem>();
		addItem("煎餅1號", "牛肉煎餅", false, 2.99);
		addItem("煎餅2號", "素食煎餅", true, 1.49);
	}
	
	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menu);
	}

	public Iterator createIterator() {
		return new PancakeIterator(menuItems);
	}
}

    咱們再也不須要getMenuItems()方法,而是用createIterator()方法代替,用來從菜單項數組建立一個迭代器,並把他返回給客戶,返回迭代器接口。客戶不須要知道餐廳菜單使如何實現維護的,也不須要知道迭代器是如何實現的。客戶只需直接使用這個迭代器遍歷菜單便可。下面修改一下客戶類的調用:

public class Waitress {
	PancakeHouseMenu pancake;
	PizzaHouseMenu pizza;
	
	public Waitress(PancakeHouseMenu pancake, PizzaHouseMenu pizza) {
		this.pancake = pancake;
		this.pizza = pizza;
	}
	
	public void printMenu() {
		Iterator pizzaIterator = pizza.createIterator();
		printMenu(pizzaIterator);
		
		Iterator pancakeIterator = pancake.createIterator();
		printMenu(pancakeIterator);
	}
	
	private void printMenu(Iterator iterator) {
		while(iterator.hasNext()) {
			MenuItem menu = (MenuItem)iterator.next();
			System.out.print(menu.getName() + ",價格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}
	}
}
public class Client {
	public static void main(String[] args) {
		PancakeHouseMenu pancake = new PancakeHouseMenu();
		PizzaHouseMenu pizza = new PizzaHouseMenu();
		
		Waitress waitress = new Waitress(pancake, pizza);
		waitress.printMenu();
	}
}

    到目前爲止,咱們將客戶調用與餐廳的菜單數據接口解耦了,客戶調用不再用爲每個不一樣數據結構的菜單編寫一套遍歷的代碼了。

咱們有些什麼……

    咱們如今使用一個共同的迭代器接口(Iteraotr)實現了兩個具體類(PizzaIterator和PancakeIterator)。這兩個具體類都實現了各自的hasNext()方法和next()方法。

    而後再PancakeHouseMenu和PizzaHouseMenu兩個類中,建立一個createIterator()方法返回各自的迭代器,在Waitress類中,使用這兩個餐廳對象返回的迭代器打印菜單。這時Waitress類和Client類不再須要關心存放菜單的數據結構,之關心能從迭代器中得到菜單就好。

    迭代器模式給你提供了一種方法,能夠順序訪問一個彙集對象的元素,而又不用知道內部是如何表示的。你已經在前面的兩個菜單實現中看到了這一點。在設計中使用迭代器的影響是明顯的:若是你有一個統一的方法訪問聚合中的每個對象,你就能夠編寫多態的代碼和這些聚合搭配,使用如同前面的printMenu()方法同樣,只要有了迭代器這個方法根本不用管菜單到底是由數組仍是集合或者其餘的數據結構來保存的。

    另一個對你的設計形成重要影響的,是迭代器模式把在元素之間遊走的責任交給迭代器,而不是聚合對象。這不只讓聚合的接口和實現變得更簡潔,也可讓聚合更專一它所應該專一的事情上面,而沒必要去理會遍歷的事情。

    讓咱們檢查類圖,未來龍去脈拼湊出來……

    先看看Aggregate接口,有一個共同的接口提供全部的聚合使用,這對客戶代碼是很方便的,將客戶代碼從集合對象的實現解耦。

    接下來看看ConcreteAggregate類,這個具體聚合持有一個對象的集合,並實現一個方法,利用此方法返回集合的迭代器。每個具體聚合都要負責實例化一個具體的迭代器,次迭代器可以便利對象集合。

    接下來是Iterator接口,這是全部迭代器都必須實現的接口,它包含一些方法,利用這些方法能夠在集合元素之間遊走。你能夠本身設計或者使用java.util.Iterator接口。

    最後是具體的迭代器,負責遍歷集合。

單一職責

    若是咱們容許咱們的聚合實現他們內部的集合,以及相關的操做和遍歷的方法,又會如何?咱們已經知道這回增長聚合中的方法個數,但又怎麼樣呢?爲何這麼作很差?

    想知道爲何,首選須要認清楚,當咱們容許一個類不但要完成本身的事情,還同時要負擔更多的責任時,咱們就給這個類兩個變化的緣由。若是這個集合變化的話,這個類也必需要改變,若是咱們遍歷的方式改變的話,這個類也必須跟着改變。因此,引出了設計原則的中心:

單一職責

一個類應該只有一個引發變化的緣由。

    類的每一個責任都有改變的潛在區域。超過一個責任,意味着超過一個改變區域。這個原則告訴咱們,儘可能讓每個類保持單一責任。

    內聚(cohesion)這個術語你應該聽過,它用來度量一個類或者模塊緊密地達到單一目的或責任。

    當一個模塊或一個類被設計成只支持一組相關的功能時,咱們說它具備高內聚;反之,當被設計成支持一組不相關的功能時,咱們說它具備低內聚。

    內聚是一個比單一職責更廣泛的概念,但二者其實關係是很密切的。遵照這個原則的類更容易有很高的凝聚力,並且比揹負許多職責的低內聚類更容易維護。

    以上就是迭代器模式的一些內容。

相關文章
相關標籤/搜索