設計模式之九 迭代器與組合模式
- 迭代器與組合模式
定義
迭代器模式提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的表示。
組合模式允許你將物件組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及物件組合
例子
廢話不多說,先看具體案例
煎餅屋和餐廳合併了,現在需要使用新的選單,但是兩者選單實現的結構不太一樣,煎餅屋使用ArrayList記錄選單,餐廳使用的是陣列。但是二者使用相同的選單項MenuItem,看下面程式碼它們的實現:
選單項程式碼
/** * 選單項 * @author Kwin * */ public class MenuItem { private String name; private String desc; private boolean vegetarian; private double price; public MenuItem(String name, String desc, boolean vegetarian, double price) { this.name = name; this.desc = desc; this.vegetarian = vegetarian; this.price = price; } public String getName() { return name; } public String getDesc() { return desc; } public boolean isVegetarian() { return vegetarian; } public double getPrice() { return price; } }
煎餅屋選單實現
/** * 煎餅屋選單 * @author Kwin * */ public class PancakeHouseMenu { ArrayList<MenuItem> menuItems; public PancakeHouseMenu() { menuItems = new ArrayList<MenuItem>(); addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99); addItem("Regular Pancake Breakfast", "Pancakes with scrambled eggs, and sausage", false, 2.99); addItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.59); } public void addItem(String name, String desc, boolean vegetarian, double price) { MenuItem menuItem = new MenuItem(name, desc, vegetarian, price); menuItems.add(menuItem); } public ArrayList<MenuItem> getMenuItems() { return menuItems; } }
餐廳選單實現
/** * 餐廳選單 * @author Kwin * */ public class DinnerMenu { static final int MAX_ITEMS = 6; int numOfItems = 0; MenuItem[] menuItems; public DinnerMenu() { 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, 3.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.00); } public void addItem(String name, String desc, boolean vegetarian, double price) { MenuItem menuItem = new MenuItem(name, desc, vegetarian, price); if(numOfItems >= MAX_ITEMS) { System.err.println("Sorry,menu is full"); } else { menuItems[numOfItems++] = menuItem; } } public MenuItem[] getMenuItems() { return menuItems; } }
現在煎餅屋打算把他的產品作為早餐,餐廳的產品則為午餐,我們需要列印新的選單,就需要兩個不同的for迴圈打印出選單,如果我們有第三家餐廳加盟(選單實現不同),就可能需要第三個迴圈。
現在我們的迭代器(Iterator)就要登場了,我們利用它來封裝“遍歷集合內的每個物件的過程”。
讓我們定義一個迭代器(Iterator)的介面:
/**
* 迭代器
* @author Kwin
*
*/
public interface Iterator {
boolean hasNext();
Object next();
}
先試試煎餅屋的迭代器:
/**
* 煎餅屋的迭代器
* @author Kwin
*
*/
public class PancakeHouseIterator implements Iterator {
ArrayList<MenuItem> menuItems;
int position = 0;
public PancakeHouseIterator(ArrayList<MenuItem> menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if(position >= menuItems.size()) {
return false;
}
return true;
}
@Override
public Object next() {
return menuItems.get(position++);
}
}
改寫煎餅屋的選單
/**
* 煎餅屋選單
* @author Kwin
*
*/
public class PancakeHouseMenu {
ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
addItem("K&B's Pancake Breakfast",
"Pancakes with scrambled eggs, and toast",
true, 2.99);
addItem("Regular Pancake Breakfast",
"Pancakes with scrambled eggs, and sausage",
false, 2.99);
addItem("Blueberry Pancakes",
"Pancakes made with fresh blueberries",
true, 3.59);
}
public void addItem(String name, String desc, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, desc, vegetarian, price);
menuItems.add(menuItem);
}
// public ArrayList<MenuItem> getMenuItems() {
// return menuItems;
// }
public Iterator createIterator() {
return new PancakeHouseIterator(menuItems);
}
}
接下來,我們實現餐廳的迭代器:
/**
* 餐館的迭代器
* @author Kwin
*
*/
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
if(position >= items.length || null == items[position]) {
return false;
}
return true;
}
@Override
public Object next() {
MenuItem menuItem = items[position++];
return menuItem;
}
}
改寫餐館的選單
/**
* 餐廳選單
* @author Kwin
*
*/
public class DinnerMenu {
static final int MAX_ITEMS = 6;
int numOfItems = 0;
MenuItem[] menuItems;
public DinnerMenu() {
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, 3.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.00);
}
public void addItem(String name, String desc, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, desc, vegetarian, price);
if(numOfItems >= MAX_ITEMS) {
System.err.println("Sorry,menu is full");
} else {
menuItems[numOfItems++] = menuItem;
// numOfItems++;
}
}
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
public Iterator createIterator() {
return new DinerMenuIterator(menuItems);
}
}
女招待有列印選單的功能,讓我們通過迭代器來實現選單的列印吧:
/**
* 女侍者
* @author Kwin
*
*/
public class Waitress {
private PancakeHouseMenu pancakeHouseMenu;
private DinnerMenu dinnerMenu;
public Waitress(PancakeHouseMenu pancakeHouseMenu, DinnerMenu dinnerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinnerMenu = dinnerMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinnerIterator = dinnerMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinnerIterator);
}
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.getDesc());
}
}
}
讓我們測試下我們的程式碼:
/**
* 測試
* @author Kwin
*
*/
public class MenuTestDrive {
public static void main(String[] args) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinnerMenu dinnerMenu = new DinnerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinnerMenu);
waitress.printMenu();
}
}
結果:
當然,Java有自己的Iterator介面,我們自己建立Iterator介面,只是為了瞭解如何從頭建立迭代器。
這是Java的介面
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
利用java.util.Iterator實現我們的選單
實現選單介面:
import java.util.Iterator;
/**
* 選單介面
* @author Kwin
*
*/
public interface Menu<E> {//
public Iterator<E> createIterator();
}
煎餅屋選單改動很簡單:
import java.util.ArrayList;
import java.util.Iterator;
/**
* 煎餅屋選單
* @author Kwin
*
*/
public class PancakeHouseMenu implements Menu<MenuItem> {
ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
addItem("K&B's Pancake Breakfast",
"Pancakes with scrambled eggs, and toast",
true, 2.99);
addItem("Regular Pancake Breakfast",
"Pancakes with scrambled eggs, and sausage",
false, 2.99);
addItem("Blueberry Pancakes",
"Pancakes made with fresh blueberries",
true, 3.59);
}
public void addItem(String name, String desc, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, desc, vegetarian, price);
menuItems.add(menuItem);
}
// public ArrayList<MenuItem> getMenuItems() {
// return menuItems;
// }
@Override
public Iterator<MenuItem> createIterator() {
return menuItems.iterator();
}
}
餐廳的迭代器:
import java.util.Iterator;
/**
* 餐館的迭代器
* @author Kwin
*
*/
public class DinerMenuIterator implements Iterator<MenuItem> {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
if(position >= items.length || null == items[position]) {
return false;
}
return true;
}
@Override
public MenuItem next() {
MenuItem menuItem = items[position++];
return menuItem;
}
@Override
public void remove() {
if(position <= 0) {
throw new IllegalStateException("You can't remove an item util you've done at least one next()");
}
if(items[position-1] != null) {
for(int i = position - 1, len = items.length - 1; i < len; i++) {
items[i] = items[i + 1];
}
items[items.length - 1] = null;
}
}
}
餐廳選單
import java.util.Iterator;
/**
* 餐廳選單
* @author Kwin
*
*/
public class DinnerMenu implements Menu<MenuItem> {
static final int MAX_ITEMS = 6;
int numOfItems = 0;
MenuItem[] menuItems;
public DinnerMenu() {
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, 3.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.00);
}
public void addItem(String name, String desc, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, desc, vegetarian, price);
if(numOfItems >= MAX_ITEMS) {
System.err.println("Sorry,menu is full");
} else {
menuItems[numOfItems++] = menuItem;
// numOfItems++;
}
}
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
@Override
public Iterator<MenuItem> createIterator() {
return new DinerMenuIterator(menuItems);
}
}
女侍者類
import java.util.Iterator;
/**
* 女侍者
* @author Kwin
*
*/
public class Waitress {
private Menu<MenuItem> pancakeHouseMenu;
private Menu<MenuItem> dinnerMenu;
public Waitress(Menu<MenuItem> pancakeHouseMenu, Menu<MenuItem> dinnerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinnerMenu = dinnerMenu;
}
public void printMenu() {
Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
Iterator<MenuItem> dinnerIterator = dinnerMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinnerIterator);
}
private void printMenu(Iterator<MenuItem> iterator) {
while(iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDesc());
}
}
}
測試類
/**
* 測試
* @author Kwin
*
*/
public class MenuTestDrive {
public static void main(String[] args) {
Menu<MenuItem> pancakeHouseMenu = new PancakeHouseMenu();
Menu<MenuItem> dinnerMenu = new DinnerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinnerMenu);
waitress.printMenu();
}
}
java.util.Enumeration(列舉)是一個有次序的迭代器實現。
現在咖啡屋也被併購進來,供應晚餐。
我們看看咖啡屋選單類
public class CafeMenu implements Menu<MenuItem> {
Hashtable<String, MenuItem> menuItems = new Hashtable<>();
public CafeMenu() {
addItem("Veggie Burger and Air Fries",
"Veggie burger on a whole wheat bun",
true, 3.29);
addItem("Soup of the day",
"Acup of the soup of the day",
false, 4.29);
}
public void addItem(String name, String desc, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, desc, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
@Override
public Iterator<MenuItem> createIterator() {
return menuItems.values().iterator();
}
}
現在我們希望能夠新增一份餐後甜點的子選單,這個時候,我們必須重新實現各家的選單了。
我們需要一種樹形結構,可以容納選單、子選單和選單項。
我們需要確定能夠在每個選單的各個項之間遊走,而且至少要像現在用迭代器一樣方便。
我們也需要能夠更有彈性地在選單項之間遊走。比方說,可能只需要遍歷甜點選單,或者可以遍歷餐廳的整個選單。
我們要用組合模式(Composite Pattern)實現這一部分
組合模式讓我們能夠用樹形方式建立物件的結構,樹裡面包含了組合以及個別的物件。
使用組合結構,我們能夠把相同的操作應用於組合和個別物件上。換句話說,在大多數情況下,我們可以忽略物件組合和個別物件之間的差別。
具體實現下面連結展示的更為清楚,這裡就偷懶不在贅述了
應用場景
優缺點
設計原則
封裝變化
多用組合,少用繼承
針對介面程式設計,不針對實現程式設計
為互動物件之間的鬆耦合設計而努力
類應該對擴充套件開放,對修改關閉
依賴抽象,不依賴具體類
最少知識原則:只和朋友交談
單一責任:一個類應該只有一個引起變化的原因
總結
在多執行緒情況下可能會有多個迭代器引用同一個物件集合,remove()可能會造成不可測的影響,必須謹慎。
小知識
單一責任原則:一個類應該只有一個引起變化的原因。
內聚(cohesion)用來度量一個類或模組緊密地達到單一目的或責任。
當一個模組或類被設計成只支援一組相關的功能時,我們說它具有高內聚;反之,當被設計為支援一組不相關的功能時,我們說它具有低內聚。
內聚是一個比單一責任原則更普遍的概念,但兩者的關係是很密切的。遵循這個原則的類很容易具有很高的凝聚力,而且比揹負許多責任的低內聚類更容易維護。