跳到主要內容

發表文章

代理人模式 (Proxy Pattern)

        代理人模式如同字面上的意思,就是做事情時(如取得某些資料),是透過代理人,而不是直接跟提供資料的物件構通。先來看看此模式的正式定義及類別圖: 代理人模式讓某個物件具有一個替身,藉以控制外界對此物件的影響。 Provide a surrogate or placeholder for another object to control access to it.         從類別圖可以看到, Subject 是共用的介面,可以讓客戶 將 Proxy 物件視為 RealSubject 物件 來處理。RealSubject 是真正做事的物件,被 Proxy 代理, Proxy 可以控制 RealSubject 的存取 。Proxy 持有 RealSubject 的參考,客戶和 RealSubject 的互動都要透過 Proxy,在某些情形下這樣的限制是必要的,如 RealSubject 是遠端物件, RealSubject 建立成本高等等。         此模式有許多種變形,都是依據上面的原則而發展出來的。以下介紹  深入淺出設計模式(Head First Design Patterns) 裡面提到的一些應用方式:         1. 遠端代理人(Remote) :算是最常使用到的應用方式。在網路中或是跨 Process 的各種程式,不可能直接存取(assess) 不同程式間的物件,因此就需要遠端代理人。 Java RMI  和 Android AIDL  都算是遠端代理人的實踐。         2. 虛擬代理人(Virtual) :代理的對象是建立很花費資源的物件。當物件建立前和建立中,由虛擬代理人扮演代理的角色。物件建立完成後,代理人就會將讓求直接轉給物件。以下為簡單範例程式: // Proxy 實作跟 RealSubject 共同的介面 Icon public class ImageProxy implements Icon { // 這是我們的 RealSubje...

狀態模式 (State Pattern)

        如果今天你要設計一台如下圖的糖果機,你會怎麼設計呢?         有上過資訊相關課程的人,應該不難從上圖聯想到 狀態圖 ,上圖中每個圓圈都是一個狀態,而每個箭頭就代表狀態的轉換。有了這個概念後,把它轉成程式就不難了: public class CandyMachine { // 以下四個值表示糖果機會用到的狀態 final static int SOLD_OUT = 0; final static int NO_COIN = 1; final static int HAS_COIN = 2; final static int SOLD = 3; // 需要有一個變數來記錄目前的狀態 // 初始設為賣完, 因為一開始機器裡沒糖果 int mState = SOLD_OUT; // 也要有另一個變數記錄目前剩多少顆糖果 int mCount = 0; public CandyMachine(int count) { mCount = count; // 機器內有糖果的話就跳到沒投錢的狀態, // 表示糖果機在等人投錢 if(mCount > 0) { mState = NO_COIN; } } // 當投錢時會執行這個方法 public void insertCoin() { if(mState == HAS_COIN) { System.out.println("You can't insert another coin"); } else if(mState == NO_COIN) { mState == HAS_COIN; System.out.println("You inserted a coin"); } ...

合成模式 (Composite Pattern)

        延續之前 反覆器模式 的菜單例子,雖然已經簡化了程式碼,但是 Waitress 裡還是要呼叫 printMenu() 多次,看起來滿醜的,要是未來有新菜單加入,Waitress 程式碼勢必要修改,這算不算是「違反開放關閉守則」?是否有什麼方式可以將菜單合併,或是只傳給 Waitress 一個反覆器,而此反覆器可以在所有菜單間遊走呢?         最簡單的改法,就是把菜單全包進一個 ArrayList,然後取得 ArrayList 的反覆器,這樣Waitress 有再多的菜單也不怕了: public class Waitress { private ArrayList<Menu> mMenus; public Waitress(ArrayList<Menu> menus) { this.mMenus = menus; } public void printMenu() { Iterator menuIterator = menus.iterator(); while(menuIterator.hasNext()) { Menu menu = (Menu) menuIterator.next(); printMenu(menu.createIterator()); } } public void printMenu(Iterator iterator) { while(iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.println(menuItem.getPrice()); } } }    ...

反覆器模式 (Iterator Pattern)

        假如你有一間早餐店,菜單用 ArrayList 存,有一間午餐店,菜單用 Array 存。今天你想把兩間餐廳合併成一間,但是馬上就遇到問題:要顯示菜單給顧客看時,要分別使用 for 來走訪 ArrayList 跟 Array,這樣造成程式維護上的困難,因為要寫兩次 for,未來要是合併另一間餐廳,又使用不同型態存菜單的話,程式碼不就又要大改? 是否有方法可以 封裝重複的動作呢 ? 答案是可以的喔,讓我們直接看程式碼來了解怎麼做。 // 這個就是菜單的共通介面 // 為了能夠走訪所有菜色 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, 要檢查是否超出長度 ...

樣板方法模式 (Template Method Pattern)

        你有一家飲料店,主要提供咖啡跟茶。泡咖啡跟泡茶的程式碼如以下所示: public class Coffee { // 這是店裡統一的泡咖啡SOP void prepareRecipe() { // 煮開水 boilWater(); // 用沸水沖泡咖啡 brewCoffeeGrinds(); // 把咖啡倒進杯子 pourInCup(); // 加糖和牛奶 addSugarAndMilk(); } // 底下省略這些方法的實作... } public class Tea { // 這是店裡統一的泡茶SOP void prepareRecipe() { // 煮開水 boilWater(); // 用沸水沖泡茶包 steepTeaBag(); // 把茶倒進杯子 pourInCup(); // 加檸檬 addSLemon(); } // 底下省略這些方法的實作... }         prepareRecipe看起來好像喔,應該可以抽象化。第一步很自然的把一些一樣的方法,如biolWater 跟 pourInCup,放到超類別裡,就稱為 Beverage。而雖然咖啡是沖泡「咖啡」、加糖跟牛奶,茶是沖泡「茶葉」、加檸檬,兩種飲料加的東西不一樣,但是做的「動作」是一樣的,只是處裡不同的原料而已。有了這些想法後,就可以來改上面的程式碼了: public abstract class Beverage { // 宣告為 final 是因為不想次類別 // 推翻這個方法, 這是統一的演算法 final void prepareRecipe() { // 煮開水 boilWater(); // 用沸水沖泡 // 跟上面程式碼比, ...

表象模式 (Facade Pattern)

        某天你心血來潮,想在家裡準備家庭劇院組。你為此做了一番研究,找來了覺得適合的播放器、投影機、螢幕、音響等等的設備,當你要準備開始看電影時,你要做哪些事呢? 將燈光調暗 開啟螢幕 打開投影機 設定投影機輸入模式 打開音響 設定音響音量 打開播放器 開始播放         將這些動作寫成程式碼的話: // 將燈光調暗到 10% 的亮度 lights.dim(10); screen.on(); projecter.on(); projecter.setInput(player); // 開啟喇叭 amp.on(); amp.setVolumn(5); player.on(); player.play();         這樣看起來做的事好像沒很多,但是當看完電影後,要把所有設備關掉要怎麼做?全部反向做一次嗎?假如要聽音樂而已,也要這麼麻煩嗎?未來升級新設備時,還要重新學習操作流程嗎?         這時候最直覺的想法一定是:把這些事包成一個 function 就好啦。這就是表象模式的精神所在,將一個或數個類別複雜的一切都隱藏起來,只露出美好的表面(就是簡化介面啦)。         使用表象模式,可以將一個複雜的次系統,變得容易使用。表象類別提供更合理的介面,來簡化原先的複雜介面。假如不想用表象介面時,還是可以直接操作次系統。         在這邊特別說明一下,表象模式跟 轉接器模式 雖然都是用來封裝類別,但是他們的目的卻是不同的。轉接器模式的目的是 改變介面以符合客戶的期望 。而表象模式是 提供次系統一個簡化的介面 。 public class HomeTheaterFacade { // 這就是合成 // 會用到的次系統元件全都在這裡 Amplifier mAmp; Player mPlayer; Projector mProjector; TheaterLights mLights; Sc...

轉接器模式 (Adapter Pattern)

        此模式從字面上來看應該不難理解,用現實生活中的例子來看,就好比出國時想讓自己帶的電器也能在國外用,但國外的插座孔跟電器插頭不同時要怎麼辦?有一個 轉接頭 就可以啦~         以程式面來看,假如你有一個系統,希望它能和一家新廠商的 library 搭配使用,但新廠商的介面卻不同於舊廠商的介面。當不能或不想修改現有的程式碼時,就可以寫一個類別,將新廠商的介面轉換成你所希望的介面。         接下來看點程式碼吧。這裡使用 策略模式 中使用的鴨子類別,並新增一組火雞類別: // 鴨子介面 public interface Duck { public void quack(); public void fly(); } // 綠頭鴨是鴨子的次類別 public class MallardDuck implements Duck { @Override public void quack() { System.out.println("Quack"); } @Override public void fly() { System.out.println("I'm flying"); } } // 火雞介面 public interface Turkey { // 火雞只會喀喀叫 public void gobble(); public void fly(); } public class WildTurkey implements Turkey { @Override public void gobble() { System.out.println("Gobble gobble"); } @Override public void fly() { System.out.println("I'm flying a short distance"); } }  ...