假如你有一間早餐店,菜單用 ArrayList 存,有一間午餐店,菜單用 Array 存。今天你想把兩間餐廳合併成一間,但是馬上就遇到問題:要顯示菜單給顧客看時,要分別使用 for 來走訪 ArrayList 跟 Array,這樣造成程式維護上的困難,因為要寫兩次 for,未來要是合併另一間餐廳,又使用不同型態存菜單的話,程式碼不就又要大改? 是否有方法可以封裝重複的動作呢? 答案是可以的喔,讓我們直接看程式碼來了解怎麼做。
看到這裡,可能有人有疑問,Java 中的 Collection 都有支援 Iterator,為什麼還要自己寫一個呢?在這邊只是為了要解說 Iterator 是怎麼被實作的,當然上面的程式碼也能改成實作 Java 內的 Iterator。看完程式碼後,接下來看看反覆器模式的正式定義吧:
// 這個就是菜單的共通介面
// 為了能夠走訪所有菜色
public interface Iterator {
// 得知是否還有更多元素
boolean hasNext();
// 取得下一個元素
Object next();
}
// 只是一個簡單的介面
// 可以取得一個反覆器, 取得菜單內容
public interface Menu {
Iterator createIterator();
}
// 以午餐店, 使用 Array 當例子
public class LunchMenuIterator implements Iterator {
private MunuItem[] mItems;
private int mPosition = 0; // 紀錄目前走訪的位置
// 建構式傳入一個菜單 Array
public LunchMenuIterator(MenuItem[] items)
{
mItems = items;
}
@Override
public Object next()
{
// 傳回目前位置的元素, 並增加位置
MenuItem menuItem = mItems[position];
position = position + 1;
return menuItem;
}
@Override
public boolean hasNext()
{
// 因為是 Array, 要檢查是否超出長度
if(position >= mItems.length || mItems[position] == null)
{
return false;
}
else
{
return true;
}
}
}
public class LunchMenu implements Menu {
private static final int MAX_ITEMS = 6;
MenuItem[] mMenuItems;
// 省略建構式及其他方法...
@Override
public Iterator createIterator()
{
return new LunchMenuIterator(mMenuItems);
}
}
修改好菜單後,就可以來看如何使用。假設店裡的服務生的工作之一就是要給客人看菜單:
public class Waitress {
private Menu mBreakfast;
private Menu mLunch;
public Waitress(Menu breakfast, Menu lunch)
{
mBreakfast = breakfast;
mLunch = lunch;
}
public void printMenu()
{
Iterator breakfastIterator = mBreakfast.createIterator();
Iterator lunchIterator = mLunch.createIterator();
printMenu(breakfastIterator);
printMenu(lunchIterator);
}
public void printMenu(Iterator iterator)
{
while(iterator.hasNext())
{
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.println(menuItem.getPrice());
}
}
// 可能還有其他方法...
}
反覆器模式讓我們能夠取得一個聚集內的每一個元素,而不需要此聚集將其實踐方式暴露出來
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
從類別圖可以看到,有一個共同的介面供所有的聚集所使用 (如 Java 中的 List),可以讓客戶程式碼從聚合的具象類別中鬆綁。而 Iterator 就是所有反覆器都要實作的介面。Iterator 可以使用 Java 現有的,也可以自己建立。具象聚集持有一個物件的聚合,並實作一個方法,可以取出相關反覆器。具象聚集為了能傳出反覆器,要負責實體化一個具象反覆器,此反覆器能夠走訪聚集內的每個元素。
另外值得一提的是,此模式很像之前介紹過的工廠方法模式,因為這兩個模式都是由次類別決定要建立的物件為何。
接下來介紹一個新的設計守則,單一責任守則:
一個類別應該只有一個改變的理由。
以這次介紹的反覆器模式來看,要是我們的聚集可以處理管理的行為,又能處理反覆的方法,這樣就有兩個理由要修改聚合程式。此守則告訴我們,盡量讓每個類別保持單一責任,就是減少改變的理由。因為類別有越多理由要修改,就越可能造成整個系統出錯。
最後再介紹一個名詞:內聚力(cohesion)。當一個類別或模組被設計成只支援一組相關功能時,可以說它有很高的內聚力,反之就是低的內聚力。遵守單一責任守則的類別容易有高的內聚力,且比有許多責任的低內聚力類別容易維護。
參考資料:
深入淺出設計模式(Head First Design Patterns)
深入淺出設計模式(Head First Design Patterns)

留言
張貼留言