跳到主要內容

發表文章

目前顯示的是 2016的文章

代理人模式 (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 { // 這是我們的 RealSubject, // 也就是真正要顯示圖的物件 private ImageIcon mImageIcon; private URL mUrl; private Thread mRetrievalThrea

狀態模式 (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"); } else if(mState

合成模式 (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, 要檢查是否超出長度 if(position

樣板方法模式 (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(); // 用沸水沖泡 // 跟上面程式碼比, 方法名更通用 brew(); // 把飲料倒

表象模式 (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; Screen mScreen; // 透過建構式將次系統元件記錄下來 public HomeTheaterFacade(Amplifier amp, Player pl

轉接器模式 (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"); } }         當你想用火雞物件來

命令模式 (Command Pattern)

         今天有一家家電廠商,想請你設計一個控制家電自動化的遙控器。遙控器上有多個插槽可控制不同家電,每個家電在遙控器上要提供開、關按鈕。廠商已提供控制家電的 API,你只需要設計遙控器 API,可以控制不同家電。注意未來可能會有新的家電。         看起來好像不好設計。既要能控制現在的家電,還要能滿足擴充性,以便控制新的家電。最重要的是,要怎麼讓「遙控器」和「家電」之間能夠鬆綁呢?有沒有一個設計模式,可以讓 「發出需求的物件」 和 「接受與執行需求的物件」 分割開呢?這時候就是命令模式派上用場的時候了。使用命令模式,遙控器只要發出需求,如開電燈,遙控器不用知道到底是誰,做了什麼事,遙控器只知道有發出需求。而需求發出後,就是接收者,電燈,去執行需求,如打開電燈。只看文字可能不是很清楚,直接用程式碼來熟悉吧。         首先我們要先有一個通用的命令介面,以便讓所有家電實做。將來遙控器也是直接對這個通用介面操作即可。 public interface Command { // 很簡單的範例,只要一個執行方法 public void execute(); }         接下來假設想實踐一個打開電燈的命令,假設廠商提供的電燈 API 裡有 on(),off(): public class LightOnCommand implements Command { // 這是廠商提供的,也是實際上處理需求的接收者 private Light mLight; // 傳入實際的電燈讓此命令能控制 // 一旦呼叫了 execute,就由此電燈物件 // 成為接收者,負責處理需求 public LightOnCommand(Light light) { mLight = light; } @Override public void execute() { // 接收者處理需求 // 此例是電燈打開,所以呼叫on() mLight.on(); } }         同理我們也可以設計出很多命令,如電燈關,風扇開、關,門開、關等命令。有了這些命令後,就可以接著

獨體模式 (Singleton Pattern)

        單看名稱就很好理解的設計模式,就是只能有一個且是唯一實體的物件。有些時候最好讓物件只能有一個以避免程式出錯,例如負責處理使用者登入的物件,假如不是獨體模式的話,登入物件就可能有多個,使用者就可能同時登入很多次。         設計此模式的想法也很簡單,不要讓別人能用 new 來建立物件就好,也就是建構子不能宣告為 public。因為其他人不能使用 private 建構子,只能在類別內使用…說那麼多,直接看程式碼比較快: public class Singleton { // 利用一個靜態變數記綠 Singleton 的實體 private static Singleton sInstance; // 可以宣告其他需要的成員變數 // 建構式宣告為 private, 這樣只有在 // Singleton 類別內才能使用 private Singleton(){} // 公開的靜態方法, 其他人要取得物件只能使用此方法 public static Singleton getInstance() { // 不為 null, 表示之前曾建立過, 不用再 new 一次 if(sInstance == null) { // 需要時才建立物件, 稱為 lazy instantiaze sInstance = new Singleton(); } return sInstance; } }         以上就是最基本的獨體模式,接下來來看一下正式定義及類別圖: 獨體模式確保一個類別只有一個實體,並給它一個存取的全域點(global point) Ensure a class has only one instance and provide a global point of access to it.         定義及實作上看起來雖然簡單,但在實務上可能會遇到問題。以上面的程式碼來看,是否真的只會有一個且唯一的物件呢 ? 以多執行緒的角度來看就可以知道,是有可能產生多個物件的,解決方法也不難,

工廠模式 - 抽象工廠模式 (Abstract Factory Pattern)

        延續前一篇 工廠方法模式 ,我們可以有多間加盟店,並且照著固定的 SOP 製作 pizza,加盟店只要專心處理 createPizza() 就好。最後還差一個步驟就能讓我們的 pizza 店更好,就是對 pizza 原料的控管。每個地區會有不同的原料,如何依照不同地區給予不同原料呢?要什麼原料就多一個工廠方法是個不錯的方法,既然會有很多原料工廠方法的話,把它們全集中在一起呢?這就是抽象工廠的概念了。我們可以先定一個抽象的原料工廠: public interface PizzaIngredientFactory { // 每個原料都是一個類別,每個原料都有一個 // 對應的方法建立該原料 public Sauce createSauce(); public Cheese createCheese(); public Vaggies createVaggies(); }         假如每個工廠的實體(子類別)都有一種通用的機制要實作,例如都要先檢查原料,清洗原料之類的,上面的例子也可以改成抽象類別。         我們接下來就可以實作紐約風的原料工廠: // 對於所有原料,紐約原料工廠都提供了紐約的版本 public class NYPizzaIngredientFactory implements PizzaIngredientFactory { @Override public Sauce createSauce() { return new NYSauce(); } @Override public Cheese createCheese() { return new NYCheese(); } @Override public Veggies createVeggies() { return new NYVeggies(); } }         接下來要修改 Pizza,讓它只使用工廠產生出來的產料: public abstract class Pizza { String name; Sauce s

工廠模式 - 工廠方法模式 (Factory Method Pattern)

        延續前一篇 簡單工廠 的 Pizza 店,當你想要開放加盟時,加盟店可能因為不同地區要有不同的口味,如要用紐約風味或芝加哥風味時,要怎麼做呢?假如用 SimplePizzaFactory 的方式,寫出不同的工廠,如 NYPizzaFactory,ChicagoPizzaFactory,這樣各地加盟店都能有適合的工廠可以使用,但是其他部份可能使用加盟店自己的流程,如切片方式,裝盒方式不同。因為工廠只管產出 Pizza,並不負責後續的處理。當你想要對 Pizza 有多一些品質控管時,應該如何做呢?         其實要改變的程式不多,只要把 createPizza() 放回 PizzaStore 中,並把這個方法設定為抽象方法,然後為每個不同地區的加盟店,建立一個 PizzaStore 次類別: // 記得要設定為抽象類別 public abstract class PizzaStore { // 為了處理訂單一致,要讓子類別不能修改此方法 final public Pizza orderPizza(String type) { Pizza pizza; // createPizza() 從工廠移回 PizzaStroe pizza = createPizza(String type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 把工廠要做的事改成這個抽象工廠方法 abstract Pizza createPizza(String type); }         orderPizza() 是被宣告在抽象的 PizzaStore 內,因此也不知道次類別如何實際製作 Pizza。而當 orderPizza() 呼叫 createPizza() 時,某個具象的次類別 PizzaStore 會負責建立 Pizza。因此我們的紐約風 Pizza 店可以這樣寫: // NYPizzaStore 也擁有 orderPizza() 及其他方法 publ

工廠模式 - 簡單工廠(Simple Factory)

        假如你有一間 pizza 店,要點餐時,你可能會這樣寫程式: Pizza orderPizza(String type) { Pizza pizza; if(type.equals("cheese")) { pizza = new CheesePizza(); } else if(type.equals("beef")) { pizza = new BeefPizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }         這很明顯可以看出有問題: 當想要更改菜單時會一直修改到 if 判斷式的程式碼, 這樣就無法讓 orderPizza 對修改關閉。         即然我們知道 if 判斷式那邊可能會一直修改,那就把他獨立出來吧: public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza; if(type.equals("cheese")) { pizza = new CheesePizza(); } else if(type.equals("beef")) { pizza = new BeefPizza(); } return pizza; } }         而原本的 pizza 程式碼就可以改寫成以下這樣: public class PizzaStore { SimplePizzaFactory mFactory; // 加入工廠 public PizzaStore(SimplePizzaFactory factory) { mFacto

裝飾者模式 (Decorator Pattern)

        假如你有一間飲料店, 目前只有賣幾種咖啡。因為生意很好, 因此想更換菜單…         以下是目前菜單的類別圖:         簡單說明此類別圖, cost() 是抽象的, 子類別要實作自己的 cost() 來告知飲料的價格。         買咖啡時, 也可要求要加料, 例如牛奶(Milk)、摩卡(Mocha,就是巧克力口味)。這樣的新類別要如何設計呢 ? 看起來是不能直接新增所需的子類別, 例如 EspressoWithMilk, EspressoWithMilkAndMocha, DarkRoastWithMilk, DarkRoastWithMilkAndMocha… 這樣加下去, 日後飲料跟配料越來越多時, 類別也就越多, 這實在不是個好設計。         換個方式設計呢, 在 Beverage 裡面加入所有的配料如何 ? 這樣好像也不太好, 未來要是配料有更動, Beverage 程式碼就要重寫, 而未來要是有新口味的飲料時, 有些配料就不太合理 ( 薑茶加摩卡 ? ), 更麻煩的是, 無法應付機車的客人 (例如要加 3 份牛奶)。這時候裝飾者模式就能上場啦。在介紹裝飾者模式前, 先說明其設計守則: 類別應該開放, 以便擴充 ; 應該關閉, 禁止修改。         我們的目標是允許類別容易擴充, 在不修改現有程式碼的情形就能搭配新的行為。這樣的設計具有彈性, 可以接受新功能以達到改變需求的目的。這看起來好像有點矛盾, 但是的確有一些技術可以在不直接修改程式碼的情形下進行擴充, 如裝飾者模式。         這時候應該有人會問: 那是不是以後我的專案架構設計都遵循這個守則就是好設計了 ? 答案是不太可能, 也沒這必要, 就算做得到, 也可能是浪費, 容易導致程式碼複雜且難以理解。只需小心選擇哪些部分未來會擴充, 這些部份遵循這個設計守則即可。         接下來正式介紹裝飾者模式的定義:  裝飾者模式動態地將責任加諸於物件上。若要擴充功能,裝飾者模提供了比繼承更有彈性的選擇 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alt

觀察者模式 (Observer Pattern)

        用來說明觀察者模式最常見也最容易懂的例子就是報社訂閱報紙: 報社的主要工作就是出版報紙 (提供資料) 用戶可向報社訂閱報紙。只要報社有出版新報紙, 而你在報社的訂閱名單中, 你都可以收到最新的報紙。 當你不想再看報紙時, 可隨時取消訂閱, 報社就不會送報紙到你家。 只要報社還存在, 用戶就可隨時向報社訂閱或取消訂閱報紙。         假如你看得懂上面的例子的話, 其實也大概了解觀察者模式在幹什麼。把上面的例子, 報社改名為主題 (Subject), 訂閱者改為觀察者 (Observer), 觀察者模式 = 出版者 + 訂閱者 。         更精確的觀察者模式定義為: 觀察者模式定義了物件之間的一對多關係, 如此一來, 當一個物件改變狀態, 其他相依者都會收到通知並自動被更新 。 Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.         以下來看個簡單的觀察者模式類別圖:         主題介面定義了 registerObserver(), removeObserver(), 可以讓物件變成觀察者, 或從觀察者名單中移除, 而觀察者介面定義了 update(), 當主題有改變時, 可以用來通知所有觀察者。在此介紹觀察者模式的設計守則: 設計時, 盡量讓需要互動的物件之間關係鬆綁 。此設計讓我們建立有彈性的 OO 系統, 能夠因應變化, 因為物件的相依性被降到最低。 關於觀察者的一切, 主題只知道觀察者有實作特定介面 (也就是 Observer 介面) 。主題不需要知道觀察者的具體類別為何、做了什麼、及其細節。 任何時候都可以加入新的觀察者 。 有新型態的觀察者出現時, 主題的程式碼不用修改 。主題不在乎觀察者實際類別, 只在乎有沒有實作觀察者介面。 片面改變主題或觀察者, 並不會影響另一方 。只要兩邊的介面沒有更改就不會影響。         了解上面的觀察者模式觀念後, 接下來就來完成報社的程式碼吧~ pubilc interface S

策略模式 (Strategy Pattern)

        假設你在公司做了一套鴨子遊戲, 類別圖如下:         某天, 主管要求你把這遊戲加上新功能: 讓鴨子會飛, 這時程式要怎麼修改呢? 最簡單的方式, 就是在父類別裡加一個 method: fly(), 這樣就所有的鴨子都會飛啦。但是這樣好像怪怪的,因為 並不是每種鴨子都會飛 。就算把不會飛的鴨子 fly() 裡什麼事都不做, 未來當不會飛的鴨子越來越多, 程式維護起來也很麻煩。         既然繼承不行,那改成介面(Interface) 應該可以吧 ?  因為不是所有鴨子都會飛跟叫, 因此就把這兩個行為拉出來變 Interface, 其類別圖如下:         但是這也不是個好的方法。一來程式碼無法再利用, 二來當鴨子的種類變多時, 每種鴨子都要自己實作 Flyable 或 Quackable, 而這時候策略模式就派上用場了。         定義:  策略模式 定義了演算法家族, 個別封裝起來,  讓它們之間可以互相替換。此模式讓演算法的變動, 不會影響到使用演算法的程式。 Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.          設計守則:         1. 把程式中可能變動的部份獨立出來         2. 寫程式是針對介面寫, 不是針對實作方式寫         3. 多用合成, 少用繼承         根據守則1, 我們知道目前會變動的就是飛跟叫的行為, 而這兩個行為在上個類別圖已經獨立出來了。根據守則2, 第一版的鴨子程式, 因為功能都加在 Duck 裡, 造成程式沒有彈性, 因此要有其他類別來實作 Flyable 跟 Quackable。更精確一點的說, 是指 Duck 想要有飛或叫的行為, 其變數的宣告應該是抽象類別或介面, 如此可使用多型, 在執行時可以依據不同的實作而執行不同的行為(不懂的話自行複習多型的觀念)。         由設計守則1 2, 我們可以重新設計這個鴨子遊戲了。當有需要飛的