跳到主要內容

發表文章

目前顯示的是 2017的文章

訪問者模式 (Visitor Pattern)

        假設你設計一個系統,其中會有一些相似類別,類別中都有某些方法內容相似,但還是需要判斷目前要做事的是哪個類別才能呼叫對應的適當類別。通常遇到這種情情,在 Java 中最直接的做法就是使用 instanceof 關鍵字來判斷,如以下的簡單範例: public interface CarComponent { public void printMessage(); } public class Wheel implements CarComponent { @Override public void printMessage() { System.out.println("This is a wheel"); } // 這是 Wheel 跟 Engine 不同的方法 public void doWheel() { System.out.println("Checking wheel..."); } } public class Engine implements CarComponent { @Override public void printMessage() { System.out.println("This is a engine"); } // 這是 Wheel 跟 Engine 不同的方法 public void doEngine() { System.out.println("Testing this engine..."); } } public class Car { private List mComponents; public Car() { mComponents = new ArrayList<carcomponent>(); } // 有些時候我們還是需要針對不同類別去做不同的事情 public void setComponent(CarCompon

原型模式 (Prototype Pattern)

        原型模式,光看字面上的意思很難了解這在幹麻,我們直接先來看正式定義及類別圖: (中文好難翻譯,我不會…) Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype.         從定義上我們大概可以知道,這個模式的用途是讓我們可以 透過複製的方式產生一個新物件 。可是產生物件幹麻要這麼麻煩,不是直接 new 出一個新物件就好了嗎? 還是一句老話: 有時建立物件的代價很高 。假如你所需要建立的物件,其所需的資料是放在遠端資料庫的,這樣當每建立一個物件,就要讀取資料庫一次,這其實是很花成本的。         從類別圖我們可以看到,有一個公開的介面 Prototype,其子類別都需要實作 clone。而 client 這邊在取得物件時,不是透過 new 的方式,而是使用 clone() 來取得所需的物件。因此我們可以知道,此模式的重點在於 client 的程式碼可以在不知道特定類別為何的情形下可以生成新的物件 。         接下來來看簡單的範例程式碼吧。在 Java 中,每個類別都繼承了 Object 這個類別,表示每個類別都含有 clone() 這個方法。但是要讓 clone() 能動,類別還必須實作 java.lang.Cloneable 這個標籤介面( Tag Interface,Marker Interface )。除了這點之外,還要注意你的 clone() 是想要淺層複製 (shallow copy) 還是深層複製 (deep copy)。 // 在 Java, 要實作 Cloneable // 才能 override Object 的 clone() 方法 public class SomeObj implements Cloneable { // 某個 primitive type 成員, // shallow copy 可以複製值 private int mNum; // 某個類別物件, // shallow copy 只會複製 reference, private Ob

備忘錄模式 (Memento Pattern)

        備忘錄模式的用途很單純,就是 提供物件回到之前狀態的功能,簡單說就是備份 (存檔) 的機制 。備忘錄模式是一個在現實世界中很常使用到的模式,如遊戲的儲存記錄,文書編輯器的「上一步」功能等。簡單介紹完這個模式後,就來看一下正式的定義及類別圖吧: 不違反封裝的情形下,取得物件的內部狀態。如此可以回復物件之前的狀態。 Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. Originator:就是定義中提到的要保留內部狀態的物件。現實例子就像是遊戲角色狀態,或是文書編輯器中的文字等。 Memento:保留 Originator 內部狀態 (資料) 的物件,例如遊戲中要存檔的資料。 Caretaker:主要功用是管理 Memento 物件。         看上面的介紹,可能有人會有疑惑,為什麼不要 Originator 自己處理存檔功能就好,還要花心力額外獨立出存檔跟管理存檔的物件?這樣做的目的其實是為了讓 類別的權責單一化(單一責任守則:一個類別應該只有一個改變的理由。) 。以生活中的例子來說,不把遊戲記錄檔獨立出來,你要怎麼拿到你朋友的超強記錄呢XD 而且 Caretaker 能管理的不是只有一個 Memento 物件,就好比文書編輯器通常不可能只「上一步」一次,要是把處理多個狀態的功能,以及保留目前狀態的功能,都放在 Originator,Originator 功能會太複雜,未來也不好維護。         看到這邊,我們應該能知道備忘錄模式有兩個目標: 儲存物件的重要狀態 維護物件的封裝         接下來來看簡單的程式碼來看備忘錄模式是如何運作的: // Originator public class GamePlayer { // 遊戲角色的生命值 private int mHp; // 遊戲角色的經驗值 private int mExp; public GamePlayer(int hp, int e

中介者模式 (Mediator Pattern)

        中介者模式,又稱栛調者模式,直接照字面上解釋,就是有一個中介者負責處理事情。因為有一個 中介者可以負責事情,如處理、傳遞、通知,就可以簡化物件之間的溝通和控制制方式,進而降低物件之間的耦合性 (相依性,依賴性)。 中介者模式定義一個可以封裝一組物件互動的物件,可以使物件不用直接互相引用而降低耦合性,且可以獨立改變物件之間的互動關係。 Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.         類別圖中的 Colleague 相關類別,指的就是實際上要做事,彼此可能因為不同的需求而會有相依性的類別。ConcreteColleague 之間不會直接溝通,而是透過 Mediator。看到這邊,可以順便跟 表象模式 來做個比較。這兩個模式都同樣有管理一群類別的味道,但不同之處是在於 Client 的角度。表象模式中,Client 是不會直接跟物件們溝通,都是透過表象來做事。而中介者模式中,Client 還是可以直接跟物件們溝通,只不過 中介者模式是強調的是物件們不會直接溝通 。另外表象模式跟中介者模式都是很好的 認識極少化守則(又稱 迪米特法則 )實踐 ,表象模式是把這個守則應用在 Client 跟要溝通的物件們,而中介者模式是把這個守則應用在物件們之間的溝通。         接下來看個簡單的範例吧。假設你現在要設計一個 GUI 的登入畫面,有四個元件:可以讓使用者輸入帳號密碼的 EditText,顯示帳號或是密碼有問題的 TextView,以及可以讓使用者確定要登入的 Button。一般來說,當帳號密碼有問題,登入 Button 就不能按,且 TextView 可能需要顯示提示訊息。當沒有使用中介者模式時,這些元件可能彼此都要互相知道才能達到上述需求,但加入了中介者模式後: // Colleague 介面 public abstract

解譯器模式 (Interpreter Pattern)

        解譯器模式簡單來說就是 把一句有特殊規則的語句,透過解釋器將它真正的意思表現出來 。相信有學過 Context-free grammar (CFG),Backus–Naur form (BNF),或是 Compiler 相關程的人會比較了解這個模式 (不是我,我早就忘光了…)。不知道上面術語的人,還是可以透過接下來的介紹來稍微了解這個模式。先來看一下這個模式的正式定義及類別圖: 定義一個語言與其文法,使用一個解譯器來表示這個語言的敘述 Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language Context 通常是指待解譯的語句 AbstractExpression 是所有規則都要實作的介面 TerminalExpression 是指無法再展開的規則,算是最小單位的規則 NonterminalExpression 是指可以再展開的規則,可以展開成 NonterninalExpression 和 TerminalExpression 的組合         從類別圖可以看到,語法可能可以一直展開,這時就可以用語法樹來表示,而語法樹以程式來表達的話,就可以使用 合成模式 。而通常比較正式的語法會用 BNF 來表示,如下: expression ::= plus | minus | variable | number plus ::= expression expression '+' minus ::= expression expression '-' variable ::= 'a' | 'b' | 'c' | ... | 'z' digit = '0' | '1' | ... | '9' number ::= digit | digit number 而 每個語法都會定義一個類別 ,如 pl

享元模式 (Flyweight Pattern)

        享元模式,又稱蠅量級模式 (Flyweight 是拳擊術語,指最輕量的級別,蠅量級),單看文字可能有點難理解,但簡單來說就是 共享元件(資源) 。試想一個情節:公司裡每個人都會有員工資訊,也都會有名片。假設名片是個類別時,對於每個員工,都需要 new 出名片的實體,而實際上名片裡也只是內容不同而已。當 員工越多時,就需要 new 出越多名片物件,造成記憶體浪費 ,這時候就適合使用享元模式。以下為享元模式的正式定義及類別圖: 共享物件,用來儘可能減少記憶體使用量以及分享資訊給儘可能多的相似物件。 Use sharing to support large numbers of fine-grained objects efficiently. Flyweight:所有具象 Flyweight 子類別的共同介面 ConcreteFlyweight:Flyweight 的具象子類別,就是要共享的元件類別。共享元件本身有內部的資料或狀態的話,都是在這裡維護。 FlyweightFactory:負責創建與管理共享元件 (如例子中的名片)。通常會確認共享元件是否已存在,存在就直接給 Client 用,否則就創建新的共享元件。 Client:取得共享元件並使用的角色,通常會結合外部資訊來使用共享元件 (如例子中的員工資訊) // 名片是共享元件 // 提供一個介面讓子類別實作 public abstract class NameCard { // 名片要顯示的資訊 protected String mId; protected String mDept; protected String mTel; public abstract void show(Employee e); } public class GeneralNameCard extends NameCard { @Override public void show(Employee e) { // 從外部資源取得資料 mId = e.getId(); mDept = e.getDept(); mTel = e.getTel();

責任鏈模式 (Chain of Responsibility Pattern)

        假設你的公司時常會收到不同的電子郵件如垃圾信,顧客的抱怨信,顧客的感謝信等等,而公司希望對這些不同類型的信都先用制式的罐頭信回應 (就是 不同的信件有不同的處理方式 )。當你要寫程式處理公司的電子郵件時,直接寫在一個 class 是最簡單的作法。但你勢必會用到 if/else 或 switch 等來判斷不同的信件,而這樣做有一些缺點,例如 程式複雜性增加,不夠彈性,擴充困難 等等。當你希望 多個物件 (處理信的方式) 都有機會能處理相同請求時 (新寄來的電子郵件) 時,可以考慮責任鏈模式。先介紹此模式的正式定義及類別圖: 讓多個物件都有機會可以處理請求,以避免請求的發送者和接受者之間產生耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞請求,直到有一個物件處理這個請求為止。 Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.         從類別圖來看這個模式很單純,每個子類別都有要實作的方法來處理它們能處理的特殊請求。 當有不能處理的請求時,也能傳給下一個子類別繼續處理 。當然這邊就是由設計者自行決定要有幾個子類別來處理哪些不同的請求,以及決定這些子類別傳遞請求的順序為何。         接著來看一下套用責任鏈模式的簡單程式碼吧 // 所有處理郵件的子類別要繼承的介面 public abstract class MailHandler { // 每個處理郵件類別都要記錄下一個 // 能處理的人是誰 protected MailHandler mHandler; public MailHandler(MailHandler handler) { mHandler = handler; } public void toNext(Mail mail) {

建立者模式 (Builder Pattern)

        今天你要為一個遊樂園設計程式,讓客人可以自由選擇旅館及各種門票、餐廳訂位、或是其他特殊活動來建立自己的假期計畫,但你可能會遇到一些問題:每個客人的 假期計畫都不一樣 ,如旅行天數不同,或是想住的旅館會不同,而且為了要滿足客製化假期內容的要求,你的設計可能會有 很多建構子 才能滿足需求: public class Vocation { // 客人只要求天數,其他隨便排 public Vocation(Date begin, Date end) { // ... } // 客人要求天數,還指定飯店 public Vocation(Date begin, Date end, Hotel hotel) { // ... } // 客人指定天數,飯店,還有餐廳 public Vocation(Date begin, Date end, Hotel hotel, Restaurant restaurant) { // ... } // 可能還有其他可以讓客人選擇的建構子 }         這樣對於客戶而言,他需要先知道全部的建構子有哪些,才能知道要選用哪個來建立適合自己的假期物件。但有時候我們提供的的建構子可能還是不符合客戶的要求,因此我們需要有一個有彈性的結構,可以把 整個假期物件的產生過程封裝起來 ,而且客戶還可以不影響物件建立的步驟,建立出自己想要的假期物件,這就是這次要介紹的建立者模式!         我們先來看看建立者模式的定義及類別圖吧 將一個複雜對象的建構和表現分離,使得同樣的建構過程可以產生出不同的表現 Separate the construction of a complex object from its representation so that the same construction process can create different representations.         從類別圖及定義可以知道,Builder 就是定義物件產生的一個介面,通常會定義一系列的方法,通常是

橋接模式 (Bridge Pattern)

        今天你要設計一個搖控器程式,想要讓不同廠商的電視都能使用的話,依我們對物件導向的了解,我們知道把搖控器抽象化成一個介面是個不錯的方法,各家廠商都依照此介面去實作他們的搖控器程式就可以。上述的類別圖看起來會如下所示:         看起來這樣的架構好像還 OK,但繼續使用下去就會發現有問題了。假如 LG,Sony 都有自己特定 API,則各家的搖控器在操作電視的 method 勢必要使用廠商的電視 API。當某天 LG 修改了 API,則所有的 LGControl 都要跟著修改。另外當哪天 RemoteControl 也需要做修改時,是不是底下的實作也需要跟著修改了?是否有一個模式,能 不只有改變實作,同時也能改變抽象 呢?答案就是接下來要介紹的模式:橋接模式。 橋接模式把抽象和實作分開,如此他們兩個可以互相獨立 Decouple an abstraction from its implementation so that the two can vary independently.          橋接模式允許你改變實作及抽象,採取的作法是 將實作與抽象放在兩個不同的類別階層中 ,由類別圖來看會比較清楚:         以我們搖控器的例子來解釋這個類別圖的話,Abstraction 就是我們的 RemoteControl。Implementor 就是我們 把容易變動的 method 獨立出來的一個介面,讓實作依賴此介面 。例如 on(),off(),setChannel() 等都是跟廠商的 API 有關係,因此把這些 method 當成介面放在 Implementor 很適合。 ConcreteImplementorA 及 ConcreteImplementorB 就是各家廠商的實作。從類別圖可以看到,當未來 Abstraction 需要做更改時,我們一樣可以創造一個 RefinedAbstraction,而不影響功能上的實作,因為會變動的 method 都移到了 Implementor。Abstraction 和 Implementor 之間的關係就叫做橋接,對於 Abstraction 的實作,不用依賴特定 API,而是透過 Implementor 來橋接到特定 API,如 Sony