1. 程式人生 > >23種設計模式分析(1):建立型模式

23種設計模式分析(1):建立型模式

  設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。

  毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使程式碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。
  GoF(“四人幫”,指Gamma, Helm, Johnson & Vlissides, Addison-Wesley四人)的《設計模式》(1995年出版)是第一次將設計模式提升到理論高度,並將之規範化,本書提出了23種基本設計模式,自此,在可複用面向物件軟體的發展過程中,新的大量的設計模式不斷出現。
  現在,可複用面向物件軟體系統現在一般劃分為三大類:應用程式、工具箱和框架(Framework)。
我們平時開發的具體軟體都是應用程式;Java的API屬於工具箱;而框架是構成一類特定軟體可複用設計的一組相互協作的類。EJB(Enterprise JavaBeans)是Java應用於企業計算的框架。
  框架通常定義了應用體系的整體結構類和物件的關係等等設計引數,以便於具體應用實現者能集中精力於應用本身的特定細節。框架主要記錄軟體應用中共同的設計決策,框架強調設計複用,因此框架設計中必然要使用設計模式。

  另外,設計模式有助於對框架結構的理解,成熟的框架通常使用了多種設計模式,如果你熟悉這些設計模式,毫無疑問,你將迅速掌握框架的結構,我們一般開發者如果突然接觸EJB JavaEE等框架,會覺得特別難學,難掌握,那麼轉而先掌握設計模式,無疑是給了你剖析EJB或JavaEE系統的一把利器。

1.1.1 設計模式分類

  各種設計模式在其粒度和抽象級別上各不相同。因為有很多的設計模式,我們需要通過某種方式來組織它們。此部分對設計模式進行分類,以便於找出相關的設計模式,而且有利於發現新的設計模式。
  我們以兩個標準來對設計模式進行分類。其中一個標準,稱為目的(Purpose),反映了這個設計模式是幹什麼的。根據其目的(Purpose),模式可分為建立(Creational)、結構(Structural)、和行為(Behavioral)。“建立型模式”關心物件的建立過程;“結構型模式”涉及類或物件的組合;“行為型模式”刻畫了類和物件互動及分配職責的方式。
  第二個標準,稱為範圍(Scope)。範圍描述了模式主要是應用於物件,還是主要應用於類。“類模式”主要處理類與其子類的關係,這種關係是通過繼承得來的,因此它們是靜態-固定、由編譯時決定的;“物件模式”處理物件關係,這種關係是執行時決定的,是動態關係。幾乎所有的模式都在某種程度上使用了繼承,因此只有那些標明為“類模式”的模式才重點關注類關係,大多數模式都在“物件模式”範疇。


  這裡,我們根據兩條準則對模式進行分類,如下圖,其中Adapter模式可以作用在類上也可以作用在物件上。


圖1-1 設計模式分類

  “建立型類模式”將部分的物件建立工作延遲到了子類,而“建立型物件模式”將其延遲到了另外的物件。“結構型類模式”應用繼承機制來合成類,而“結構型物件模式”則規定了裝配物件的方式。行為型類模式用繼承機制來描述演算法和控制流,而行為型物件模式則規定一組物件如何合作以完成某項單一物件不能達成的任務。
  GOF的《設計模式》中把23個設計模式分成三大類:Creational(建立型)5個;Structural(結構型)7個;Behavioral(行為型)11個。
  建立型:Factory Method, Abstract Factory, Builder, Prototype, Singleton共5個;
  結構型:Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy共7個;
  行為型:Observer, State, Strategy, Visitor, Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento共11個。
  組織模式有另外的方式。有些模式通常在一起使用,例如,複合模式通常與迭代器模式或訪問者模式一同使用。有些設計模式是可替換的,如原型模式常常替換抽象工廠模式。有些設計模式的設計是相似的,雖然它們各有不同的意圖,如複合模式與裝飾模式的結構圖非常相似。
  另一種方式是根據模式的“相關模式”部分所描述的它們怎樣互相引用來組織設計模式。下圖給出了模式關係的圖形說明。

圖1-2 按相關模式來組織設計模式

  顯然,存在著許多組織設計模式的方法。從多角度去思考模式有助於對它們的功能、差異和應用場合的更深入理解。
  23個設計模式描述如下:
  1.Factory Method工廠方法模式:
定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。Factory Method使一個類的例項化延遲到其子類。
  2.Abstract Factory抽象工廠模式:提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。
  3.Builder建造者模式:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
  4.Prototype原型模式:用原型例項指定建立物件的種類,並且通過拷貝這個原型來建立新的物件。
  5.Singleton單例模式:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
  6.Adapter介面卡模式:將一個類的介面轉換成客戶希望的另外一個介面。Adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。
  7.Bridge橋接模式:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
  8.Composite複合模式:將物件組合成樹形結構以表示“部分-整體”的層次結構。Composite使得客戶對單個物件和複合物件的使用具有一致性。
  9.Decorator裝飾模式:動態地給一個物件新增一些額外的職責。就擴充套件功能而言,Decorator模式比生成子類方式更為靈活。
  10.Facade外觀模式:為子系統中的一組介面提供一個一致的介面, Facade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
  11.Flyweight享元模式:運用共享技術有效地支援大量細粒度的物件。
  12.Proxy代理模式:為其他物件提供一個代理以控制對這個物件的訪問。
  13.Interpreter 直譯器模式:給定一個語言,定義它的文法的一種表示,並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。
  14.Template Method模板方法模式:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
  15.Chain of Responsibility職責鏈模式:為解除請求的傳送者和接收者之間耦合,而使多個物件都有機會處理這個請求。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它。
  16.Command命令模式:將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可取消的操作。
  17.Iterator迭代器模式:提供一種方法順序訪問一個聚合物件中各個元素,而又不需暴露該物件的內部表示。
  18.Mediator中介者模式:用一箇中介物件來封裝一系列的物件互動。中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
  19.Memento 備忘錄模式:在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可將該物件恢復到儲存的狀態。
  20.Observer觀察者模式:定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動重新整理。
  21.State狀態模式:允許一個物件在其內部狀態改變時改變它的行為。物件看起來似乎修改了它所屬的類。
  22.Strategy策略模式:定義一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得演算法的變化可獨立於使用它的客戶。
  23.Visitor訪問者模式:表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。

1.1.2 Simple Factory / Factory Method / Abstract Factory

  1. Simple Factory Pattern(簡單工廠模式)

  專門定義一個類來負責建立其他類的例項,被建立的例項通常都具有共同的父類。簡單工廠(Simple Factory)模式又稱為靜態工廠方法(Static Factory Method)模式,屬於類的建立型模式,通常它根據變數的不同返回不同的類的例項。UML類圖如下:

圖2-1 簡單工廠模式

  簡單工廠模式的實質是由一個工廠類根據傳入的參量,動態決定應該創建出哪一個產品類的例項。簡單工廠模式實際上不屬於23個GoF模式,但它可以作為GoF的工廠方法模式(Factory Method)的一個引導。從上圖可以看出,簡單工廠模式涉及到工廠角色、抽象產品角色和具體產品角色三個參與者。
  (1) 工廠(Creator)角色:是簡單工廠模式的核心,它負責實現建立所有例項的內部邏輯。工廠類可以被外界直接呼叫,建立所需的產品物件。
  (2) 抽象產品(Product)角色:是簡單工廠模式所建立的所有物件的父類,它負責描述所有例項所共有的公共的介面。
  (3) 具體產品(Concrete Product)角色:是簡單工廠模式的建立目標,所有建立的物件都是充當這個角色的某個具體類的例項。

  例項: 電子付款系統
  在電子付款系統中,會存在很多種電子交易方式,包括虛擬支票、信用卡、有線傳送等。


圖2-2 電子付款方式

  實現如下:

//抽象電子付款類
abstract class EFT {

    abstract void process();
}

//具體子類,虛擬支票
class VirtualCheck extends EFT {

    @Override
    public void process() {
        System.out.println("虛擬支票處理中");
    }
}

//具體子類,萬事達卡
class MasterCard extends EFT {

    @Override
    public void process() {
        System.out.println("萬事達卡處理中");
    }
}

//簡單工廠類
class EFTFactory {

    public EFT createEFT(String type) {
        switch (type.toLowerCase()) {
            case "virtualcheck":
                return new VirtualCheck();
            case "mastercard":
                return new MasterCard();
            default:
                return null;
        }
    }
}

//客戶應用測試
class Client {

    public static void main(String[] args) {
        EFT eft;
        EFTFactory eftFactory = new EFTFactory();
        eft = eftFactory.createEFT("VirtualCheck");
        eft.process();
        eft = eftFactory.createEFT("MasterCard");
        eft.process();
    }
}
  優勢和缺陷:
  在簡單工廠模式中,工廠類是整個模式的關鍵所在。它包含必要的判斷邏輯,能夠根據外界給定的資訊,決定究竟應該建立哪個具體類的物件。通過使用工廠類,外界可以從直接建立具體產品物件的尷尬局面中擺脫出來,僅僅需要負責“消費”物件就可以了,而不必管這些物件究竟是如何建立以及如何組織的。這樣就明確區分了各自的職責和權力,有利於整個軟體體系結構的優化。
  不過,凡事有利就有弊,簡單工廠模式的缺點也正體現在其工廠類上。由於工廠類集中了所有例項的建立邏輯,很容易違反GRASP的高內聚的責任分配原則。將全部建立邏輯都集中到一個工廠類還有另外一個缺點,那就是當系統中的具體產品類不斷增多時,可能會出現要求工廠類根據不同條件建立不同例項的需求。這種對條件的判斷和對具體產品類的判斷交錯在一起,很難避免模組功能的蔓延,對系統的擴充套件和維護也非常不利。另外,只用一個類根據if分支來決定建立哪一型別的物件,一旦需要擴充套件,就要修改類的程式碼增加if分支,破壞了開閉原則(Open Closed Principle, OCP,即一個軟體實體應當對擴充套件開放,對修改關閉。換句話說,我們在設計一個模組的時候,應當使這個模組在不被修改的前提下可以被擴充套件)。
  應用情景:
  下列情況適於應用簡單工廠模式:
  (1)  工廠類負責建立的物件比較少。
  (2)  客戶只知道傳入工廠類的引數,對於如何建立物件(邏輯)不關心。
  由於簡單工廠很容易違反GRASP的高內聚責任分配原則,因此一般只在很簡單的情況下應用。

  2. Factory Method Pattern (工廠方法模式)

  定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。Factory Method使一個類的例項化延遲到其子類。工廠方法模式又稱工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多型工廠(Polymorphic Factory)模式,屬於類的建立型模式。

  在簡單工廠模式中,一個工廠類處於對產品類進行例項化的中心位置,它知道每一個產品類的細節,並決定何時哪一個產品類應當被例項化。簡單工廠模式的優點是能夠使客戶端獨立於產品的建立過程,並且在系統中引入新產品時無需對客戶端進行修改,缺點是當有新產品要加入系統中時,必須修改工廠類,以加入必要的處理邏輯。簡單工廠模式的致命弱點就是處於核心地址的工廠類,因為一旦它無法確定要對哪個類進行例項化,就無法使用該模式,而工廠方法模式則可以很好地解決這一問題。
  工廠方法模式的UML圖如下:

圖2-3 工廠方法模式

  其中的類或物件之間的關係為:
  (1) Product(產品角色):定義產品的介面。
  (2) ConcreteProduct(真實的產品):實現介面的Product的類。
  (3) Creator(工廠角色):宣告工廠方法(FactoryMethod),返回一個產品。
  (4)  ConcreteCreator(真實的工廠):實現FactoryMethod工廠方法,由客戶呼叫,返回一個產品的例項。

  例項1:Windows的COM元件與MFC

  應用Factory Method的最典型的例子當數COM的類廠,如果沒有類廠的幫助,而是將元件類的建立工作交由我們自己完成,則我們必須知道元件類的類名,而如果這樣,則COM的封裝性和可重用性都將大大受損。正是由於運用了Factory Method,COM元件只負責對外提供介面,封裝內部實現,包括建立過程的特性才得以完整體現。在MFC中,CDocTemplate是另一個典型的應用Factory Method的例子,MFC實現中通過派生不同的子類CSingleDocTemplate/CMultiDocTemplate來支援不同的文件型別,感興趣的朋友可以自己研究CDocTemplate及其子類的相關程式碼,相關程式碼在afxwin.h中,與上面討論的Factory Method不同的是,這裡的文件型別(單文件/多文件)並沒有嚴格一致的類與之對應。

  例項2: 多文件系統
  應用工廠方法模式提供的最大靈活性生成不同的物件。抽象類可以返回一個預設的物件,而每一個派生的子類都可以返回擴充套件了的其他物件。

圖2-4 多文件系統

  實現如下:

import java.util.ArrayList;

//產品基類
abstract class Page {
}

//工廠抽象類
abstract class Document {

    protected ArrayList<Page> pages = new ArrayList<>();

    public Document() {
        this.createPages();
    }

    public ArrayList<Page> getPages() {
        return pages;
    }

    //工廠方法
    abstract public void createPages();
}

//具體產品類——技能頁
class SkillsPage extends Page {

}

//具體產品類——教育頁
class EducationPage extends Page {

}

//具體產品類——經驗頁
class ExperiencePage extends Page {
}

//具體產品類——介紹頁
class IntroductionPage extends Page {
}

//具體產品類——結果頁
class ResultPage extends Page {
}

//具體產品類——結論頁
class ConclusionPage extends Page {
}

//具體產品類——總結頁
class SummaryPage extends Page {
}

//具體產品類——文獻頁
class BibliographyPage extends Page {
}

//具體工廠類——個人簡歷,包括技能、教育、經驗
class Resume extends Document {

    public void createPages() {
        pages.add(new SkillsPage());
        pages.add(new EducationPage());
        pages.add(new ExperiencePage());
    }
}

//具體工廠類——報告,包括介紹、結果、結論、總結、文獻
class Report extends Document {

    @Override
    public void createPages() {
        pages.add(new IntroductionPage());
        pages.add(new ResultPage());
        pages.add(new ConclusionPage());
        pages.add(new SummaryPage());
        pages.add(new BibliographyPage());
    }
}

//客戶應用測試
class MyClient {

    public static void main(String[] args) {
        Document[] docs = new Document[2];
        docs[0] = new Resume();
        docs[1] = new Report();
        for (Document document : docs) {
            System.out.println("\n" + document + "--------------");
            for (Page page : document.getPages()) {
                System.out.println("\t" + page);
            }
        }
    }
}
  例項3:手機工廠
  現實中不同品牌的手機應由不同的工廠製造,下面手機工廠的示例,所應用的模式就是工廠方法(FactoryMethod)模式。

圖2-5 手機工廠

//手機介面
interface Mobile {

    public void Call();
}

//手機工廠介面
interface MobileFactory {

    //工廠方法
    Mobile produceMobile();
}

//摩托羅拉手機:實現手機介面
class Motorola implements Mobile {

    @Override
    public void Call() {
        System.out.println("摩托羅拉手機");
    }
}

//諾基亞手機:實現手機介面
class Nokia implements Mobile {

    @Override
    public void Call() {
        System.out.println("諾基亞手機");
    }
}

//摩托羅拉工廠:實現生產手機的方法,返回摩托羅拉手機
class MotorolaFactory implements MobileFactory {

    @Override
    public Mobile produceMobile() {
        System.out.println("摩托羅拉工廠製造了");
        return new Motorola();
    }
}

//諾基亞工廠:實現生產手機的方法,返回諾基亞手機
class NokiaFactory implements MobileFactory {

    @Override
    public Mobile produceMobile() {
        System.out.println("諾基亞工廠製造了");
        return new Nokia();
    }
}

//客戶應用測試
class ClientForTest {

    public static void main(String[] args) {
        MobileFactory mf;
        Mobile m;
        mf = new MotorolaFactory();
        m = mf.produceMobile();
        m.Call();
        mf = new NokiaFactory();
        m = mf.produceMobile();
        m.Call();
    }
}
  優勢和缺陷:

  在工廠方法模式中,工廠方法用來建立客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被例項化這一細節。工廠方法模式的核心是一個抽象工廠類,各種具體工廠類通過抽象工廠類將工廠方法繼承下來。如此使得客戶可以只關心抽象產品和抽象工廠,完全不用理會返回的是哪一種具體產品,也不用關係它是如何被具體工廠建立的。
  (1)  基於工廠角色和產品角色的多型性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定建立何種產品物件,而如何建立這個物件的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多型工廠模式,就正是因為所有的具體工廠類都具有同一抽象父類。
  (2) 使用工廠方法模式的另一個優點是在系統中加入新產品時,無需修改抽象工廠和抽象產品提供的介面,無需修改客戶端,也無需修改其他的具體工廠和具體產品,而只要新增一個具體工廠和具體產品就可以了,這符合開閉原則,從而使得系統的可擴充套件性非常好。優秀的面向物件設計鼓勵使用封裝(Encapsulation)和委託(Delegation)來構造軟體系統,工廠方法模式正是使用了封裝和委託的典型例子,其中封裝是通過抽象工廠來體現的,而委託則是通過抽象工廠將建立物件的責任完成交給具體工廠來體現。
  (3) 使用工廠方法模式的缺點是在新增新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,當兩者都比較簡單時,系統會有相對額外的開銷。

  3. Abstract Factory Pattern(抽象工廠模式)

  提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。抽象工廠(Abstract Factory)模式又稱為Kit模式,屬於物件建立型模式。
  抽象工廠模式與工廠方法模式最大的區別在於:工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則針對的是多個產品等級結構。正因如此,在抽象工廠模式中經常會用到產品族(Product Family)這一概念,它指的是位於不同的產品登記結構中,並且功能互相關聯的產品系列,如下圖:

圖2-6 產品族

  圖上三個箭頭所指就是三個功能相互關聯的產品,它們位於三個不同的產品登記結構中的相同位置上,共同組成了一個產品族。抽象工廠就是要生成這樣的產品族。而在這種產品族中,各產品之間有關聯耦合,抽象工廠會將這種關聯耦合設計成一個抽象類。抽象工廠模式符合GRASP的純虛構模式,同時取得高內聚低耦合的效果。
  其UML類圖如下:

圖2-7 抽象工廠模式

  其中的類或物件之間的關係為:
  (1) AbstractFactory(抽象工廠):宣告生成抽象產品的方法。
  (2) ConcreteFactory(具體工廠):執行生成抽象產品的方法,生成一個具體的產品。
  (3) AbstractProduct(抽象產品):為一種產品宣告介面。
  (4) Product(具體產品):定義具體工廠生成的具體產品的物件,實現產品介面。
  (5) Client(客戶):我們的應用程式,使用抽象產品和抽象工廠生成物件。

  抽象工廠負責建立不同的有聯絡的多個產品,不同的抽象工廠建立的產品不同,但產品之間的關係相同,抽象工廠是GRASP模式的純虛構的表現。

  例項1: 大陸生態系統
  大家都知道,動物世界中各大陸的動物是不一樣的,各種動物可以分成兩樣,一種食草,一種食肉。食肉的動物吃食草動物。美洲狼屬於食肉動物,野牛屬於食草動物,美洲狼獵吃野牛;非洲的獅子屬於食肉動物,角馬屬於食草動物,獅子獵吃角馬。類的關係圖如下:

圖2-8 大陸生態系統

//抽象大陸工廠
abstract class ContinentFacctory {

    abstract public Herbivore createHerbivore();

    abstract public Carnivore createCarnivore();
}

//非洲大陸,有角馬、獅子
class AfricaFactory extends ContinentFacctory {

    @Override
    public Herbivore createHerbivore() {
        return new Wildebeest();
    }

    @Override
    public Carnivore createCarnivore() {
        return new Lion();
    }
}

//美洲大陸,有野牛、狼
class AmericaFactory extends ContinentFacctory {

    @Override
    public Herbivore createHerbivore() {
        return new Bison();
    }

    @Override
    public Carnivore createCarnivore() {
        return new Wolf();
    }
}

//食草動物
abstract class Herbivore {
}

//食肉動物:吃食草動物
abstract class Carnivore {

    abstract public void eat(Herbivore h);
}

//角馬
class Wildebeest extends Herbivore {
}

//獅子
class Lion extends Carnivore {

    @Override
    public void eat(Herbivore h) {
        System.out.println(this + " eats " + h);
    }
}

//野牛
class Bison extends Herbivore {
}

//狼
class Wolf extends Carnivore {

    @Override
    public void eat(Herbivore h) {
        System.out.println(this + " eats " + h);
    }
}

//動物世界
class AnimalWorld {

    private Herbivore herbivore;
    private Carnivore carnivore;

    //建立兩種動物分類
    public AnimalWorld(ContinentFacctory factory) {
        carnivore = factory.createCarnivore();
        herbivore = factory.createHerbivore();
    }

    //執行食物鏈
    public void RunFoodChain() {
        carnivore.eat(herbivore);
    }
}

//客戶應用測試
class ClientAnimal {

    public static void main(String[] args) {
        //創造並執行非洲動物世界
        ContinentFacctory africa = new AfricaFactory();
        AnimalWorld world = new AnimalWorld(africa);
        world.RunFoodChain();

        //創造並執行美洲動物世界
        ContinentFacctory america = new AmericaFactory();
        world = new AnimalWorld(america);
        world.RunFoodChain();
    }
}
  例項2: 電腦產品
  IBM,Dell都是著名的計算機生產廠家,他們採用的主辦、硬碟及CPU。但配件間、主機板與CPU一定要互相相容。例如下面例子中的微星MSIK7N2G配AMD的CPU;微星MSI865PE配Intel的CPU。類圖如下:

圖2-9 電腦產品

package com.demo.designpattern;

//定義CPU介面
interface CPU {

    String designCPU();
}

//定義AMD類,實現CPU介面
class AMD implements CPU {

    @Override
    public String designCPU() {
        return "Athlon XP 2800+";
    }
}

//定義Intel類,實現CPU介面
class Intel implements CPU {

    @Override
    public String designCPU() {
        return "奔騰4 3.2C";
    }
}

//定義硬碟介面
interface HardDisc {

    String designHardDisc();
}

//定義Maxtor類,實現硬碟介面
class Maxtor implements HardDisc {

    @Override
    public String designHardDisc() {
        return "MaXLine Plus II 200G";
    }
}

//定義WestDigit類,實現硬碟介面
class WestDigit implements HardDisc {

    @Override
    public String designHardDisc() {
        return "WD2500JD 250G";
    }
}

//定義主機板介面,包含引數為CPU的公共方法Attach()
interface MainBoard {

    void Attach(CPU cpu) throws Exception;
}

//主機板微星MSI865PE,支援Intel的CPU
class MSI865PE implements MainBoard {

    @Override
    public void Attach(CPU icpu) throws Exception {
        if ("com.demo.designpattern.intel".equals(icpu.getClass().getName().toLowerCase())) {
            System.out.println("MSI865PE");
        } else {
            throw new Exception("主機板MSI865PE只能配Intel的CPU");
        }
    }
}

//主機板微星MSIK7N2G,支援AMD的CPU
class MSIK7N2G implements MainBoard {

    @Override
    public void Attach(CPU icpu) throws Exception {
        if ("com.demo.designpattern.amd".equals(icpu.getClass().getName().toLowerCase())) {
            System.out.println("MSIK7N2G");
        } else {
            throw new Exception("主機板MSIK7N2G只能配AMD的CPU");
        }
    }
}

//定義抽象電腦工廠類
abstract class ComputerFactory {

    protected CPU icpu;
    protected HardDisc iHD;
    protected MainBoard iMB;

    public void Show() {
        try {
            System.out.println(this.getClass().getName() + "生產的電腦配置");
            System.out.println("CPU: " + icpu.designCPU());
            System.out.println("HardDisk: " + iHD.designHardDisc());
            System.out.print("MainBoard: ");
            iMB.Attach(icpu);
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}

//抽象電腦工廠類派生類IBM,定義其返回的系列配件產品
class IBM extends ComputerFactory {

    public IBM() {
        icpu = new Intel();
        iHD = new WestDigit();
        iMB = new MSI865PE();
    }
}

//抽象電腦工廠類派生類DELL,定義其返回的系列配件產品
class Dell extends ComputerFactory {

    public Dell() {
        icpu = new AMD();
        iHD = new Maxtor();
        iMB = new MSIK7N2G();
    }
}

//客戶應用測試
class ClientComputer {

    public static void main(String[] args) {
        IBM ibm = new IBM();
        ibm.Show();
        Dell dell = new Dell();
        dell.Show();
    }
}
  優勢和缺陷:
  抽象工廠模式的主要優點是隔離了具體類的生成,使得客戶不需要知道什麼被建立了。這種隔離使得更換一個具體工廠就變得相對容易。所有的具體工廠都實現了抽象工廠中定義的那些公共介面,因此只需改變具體工廠的例項,就可以在某種程度上改變這個軟體系統的行為。另外,應用抽象工廠模式符合GRASP純虛構的模式,可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛應用。

  我們在建立這些物件的時候,並不需要指定它們的具體類,這些具體類的物件是由工廠物件負責例項化的。主要的好處是,客戶程式碼不僅不知道工廠類的具體型別,而且也不知道具體的產品型別,具體的產品型別資訊被封裝到具體的工廠中了。所以,客戶類只操縱工廠介面和產品介面,不知道具體的工廠產生產品的實現細節,建立的複雜性被封裝了。
  使用抽象工廠模式的最大好處是,當一個產品族中的多個物件被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的物件。這對一些需要根據當前環境來決定其行為的軟體系統來說,是非常使用的一種設計模式。
  抽象工廠模式的缺點是在新增新的產品物件時,難易擴充套件抽象工廠以便生產新種類的產品。這是因為AbstractFatory介面規定了所有可能被建立的產品集合,要支援新種類的產品就意味著要對該介面進行擴充套件,而這將涉及到對AbstractFatory及其所有子類的修改。
  設計模式提倡的是:“優先使用組合,而不是繼承”。但abstract factory模式中,並未使用組合,而只是依賴(例項化),也就是說abstract factory模式的內部,仍然有不小的耦合。設計模式的主要目標是“把變化的和不變的分離,提高彈性”。但在abstract factory中,彈性是有限的。即你可以從抽象基類派生出不同的具體工廠實現生產不同的具體產品,但所有這些都受限於你的介面,如果產品的改變非常之大,以至於你的介面變化了,那麼客戶類的程式碼也得隨之改變。也就是說,減小耦合失敗了。總之,當介面發生變化時候,所有實現類都需要做改動。還可能導致相關的客戶端都需要重新改動,如果介面呼叫順序又相互關聯的話,那問題就更多了。

  基本上來說,Abstract Factory模式和Factory Method模式所作的事情是一樣的,都是用來建立與具體程式程式碼無關的物件,只是面對的物件層次不一樣,Abstract Factory建立一系列的物件組,這些物件彼此相關。而Factory Method往往只是建立單個的物件。
  這裡有必要先陳敘一個在設計模式,或者說在整個面向物件設計領域所遵循的一個設計原則:針對介面程式設計,而不是針對具體的實現。這個思想可以說是設計模式的基石之一。現在的很多物件模型,比如EJB,COM+等等,無不是遵照這個基本原則來設計的。針對介面程式設計的好處有很多,通過介面來定義物件的抽象功能,方便實現多型和繼承;通過介面來指定物件呼叫之間的契約,有助於協調物件之間的關係;通過介面來劃分物件的職責,有助於尋找物件,等等。
  Abstract Factory和Factory Method,還有其他的一些建立型的設計模式,都是為了實現這個目的而設計出來的。它們建立一個個符合介面規範的物件/物件組,使得用同一個Factory創建出來的物件/物件組可以相互替換。這種可替換性就稱為多型,是面向物件的核心思想之一。而多型,是通過動態繫結來實現的。

  應用情景:

  在必須協調一組物件的建立時,可以應用Abstract Factory模式。它提供了一種方式,將如何執行物件例項化的規則從使用這些物件的客戶物件中提取出來。首先,找出例項化的規則,定義了一個帶介面的抽象類,其中的介面為每種需要例項化的物件提供一個方法。然後,從這個類為每個組實現具體類。最後,由客戶物件決定使用具體工廠來建立所需的物件。它主要適用於以下幾種情況:

  (1) 系統需要遮蔽有關物件如何建立、如何組織和如何表示。
  (2) 系統需要由關聯的多個物件來構成。
  (3) 有關聯的多個物件需要一起應用並且它們的約束是強迫的(不可分離)。
  (4) 你想提供一組物件而不顯示他們的實現過程,只顯示它們的介面。

  三種Factory模式的比較:
  Simple Factory在於對產品建立過程的簡單封裝,它簡單地根據輸入來決定建立何種產品(這些產品不一定屬於同一產品族),因此,任何產品種類的更新都將對Simple Factory的程式碼造成影響;Factory Method面對的是一個產品族,它引入了ConcreteFactory來決定建立產品族中的何種產品,當產品種類增加時,只需建立新的ConcreteFactory來建立新的產品;而Abstract Factory面對的則是多個產品系列,它是Factory Method的延伸,Abstract Factory在一個ConcreteFactory中包含了多個Factory Method,以用於建立多個不同產品族中的多個產品。
  需要注意的是,以上所說的產品並非僅限於單個的產品,可以包括一次創建出來的一組相同或者相關產品,從這個意義上講,三種Factory特別是Factory Method與Abstract Factory之間的界限並非十分明顯。
  Abstract Factory著重於建立一系列相關的物件,而這些物件與具體的Abstract Factory相關。而Factory Method則著重於建立單個的物件,這個物件決定於一個引數或者一個外部的環境變數的值;或者,在一個抽象類中定義一個抽象的工廠方法(也成為虛擬構造器),然後在實現的子類中返回具體的產品物件。
  Factory Method可以藉助一個引數或者一個外部的標誌來判斷該具體生成哪一個子類的例項。比如對於不同的具體情況,需要有不同的Abstract Factory來生成相應的物件組。這時候,Factory Method有時也可作為一個Abstract Factory物件的靜態方法出現(稱為簡單工廠方法),使得其能夠在具體的物件被建立之前就能夠被呼叫。
  在JAVA中,應用工廠模式的地方實在太多,下面我們來看一個在JAXP中這兩個模式的應用。JAXP是用來處理XML文件的一個API。我們都知道XML檔案的一個特點就是其平臺無關,流通效能好。因而往往也需要處理他們的程式具有更好的平臺無關性。Java語言是一個比較好的平臺無關語言,可以作為一個選擇,但是對XML進行解析的解析器確有很多。有時候需要在不同的解析器之間進行切換,這時候,JAXP的良好設計就能夠體現出來了。它能夠允許在不同解析器之間竟進行切換的時候,不用更改程式的程式碼。
  我們再拿JAXP中的DOM解析器來作為例子,來例示Abstract Factory和Factory Method的用法。

圖2-10 DOM中工廠模式的應用

  上圖中為了方便起見,只畫出了抽象類和介面,DocumentBuilderFactory和DocumentBuilder都是抽象類。
  DocumentBuilderFactory的靜態方法newInstance()根據一個外部的環境變數javax.xml.parsers.DocumentBuilderFactory的值來確定具體生成DocumentBuilderFactory的哪一個子類。這兒的newInstance()是一個工廠方法。當DocumentBuilderFactory被建立後,可以呼叫其newDocumentBuilder()來建立具體一個DocumentBuilder的子類。然後再由DocumentBuilder來生成Document等DOM物件。
  下面是建立一個DOM物件的程式碼片段:

//第一步:建立一個DocumentBuilderFactory。
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//第二步:建立一個DocumentBuilder
DocumentBuilder db = dbf.newDocumentBuilder();
//第三步:解析XML檔案得到一個Document物件
Document doc = db.parse(new File(filename)); 
  在這兒,DocumentBuilderFactory是一個抽象工廠,其newInstance()方法是一個靜態的工廠方法,DocumentBuilder,Document,Node等等物件所組成的一個產品組,是和DocumentBuilderFactory相關的。這也就是Factory Method模式的含義所在。
  當然,Factory Method模式應用的很廣。這是一個具體的例子,但他不應該限制我們的思路,Factory Method和Abstract Factory是解決面向物件設計中一個基本原則“面向介面程式設計”的主要方法。
  使用注意事項:

  (1) Factory Method模式的兩種情況:一是工廠方法在抽象類中,它不提供它所宣告的工廠方法的實現;二是工廠方法在具體的或抽象類中均可,且它提供一個工廠方法的預設實現,這時候的工廠方法通常是靜態的。
  (2) 工廠方法是可以帶引數的。
  (3) 工廠的作用並不僅僅只是建立一個物件,它還可以做物件的初始化,引數的設定等。