超全的設計模式簡介(45 種)

guanguans發表於2019-04-16

該文建議配合 design-patterns-for-humans 中文版 一起看。

推薦閱讀

設計模式(Design pattern)代表了最佳的實踐,通常被有經驗的物件導向的軟體開發人員所採用。設計模式是軟體開發人員在軟體開發過程中面臨的一般問題的解決方案。這些解決方案是眾多軟體開發人員經過相當長的一段時間的試驗和錯誤總結出來的。
設計模式是一套被反覆使用的、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使程式碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。專案中合理地運用設計模式可以完美地解決很多問題,每種模式在現實中都有相應的原理來與之對應,每種模式都描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是設計模式能被廣泛應用的原因。

設計模式的型別

共有 23 種設計模式。這些模式可以分為三大類:

下面用一個圖片來整體描述一下設計模式之間的關係:

設計模式的六大原則

1、開閉原則(Open Close Principle)

開閉原則的意思是:對擴充套件開放,對修改關閉。在程式需要進行擴充的時候,不能去修改原有的程式碼,實現一個熱插拔的效果。簡言之,是為了使程式的擴充套件性好,易於維護和升級。想要達到這樣的效果,我們需要使用介面和抽象類,後面的具體設計中我們會提到這點。

2、里氏代換原則(Liskov Substitution Principle)

里氏代換原則是物件導向設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。LSP 是繼承複用的基石,只有當派生類可以替換掉基類,且軟體單位的功能不受到影響時,基類才能真正被複用,而派生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

3、依賴倒轉原則(Dependence Inversion Principle)

這個原則是開閉原則的基礎,具體內容:針對介面程式設計,依賴於抽象而不依賴於具體。

4、介面隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的介面,比使用單個介面要好。它還有另外一個意思是:降低類之間的耦合度。由此可見,其實設計模式就是從大型軟體架構出發、便於升級和維護的軟體設計思想,它強調降低依賴,降低耦合。

5、迪米特法則,又稱最少知道原則(Demeter Principle)

最少知道原則是指:一個實體應當儘量少地與其他實體之間發生相互作用,使得系統功能模組相對獨立。

6、合成複用原則(Composite Reuse Principle)

合成複用原則是指:儘量使用合成 / 聚合的方式,而不是使用繼承。

工廠模式

工廠模式(Factory Pattern)最常用的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。

介紹

意圖: 定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

主要解決: 主要解決介面選擇的問題。

何時使用: 我們明確地計劃不同條件下建立不同例項時。

如何解決: 讓其子類實現工廠介面,返回的也是一個抽象的產品。

關鍵程式碼: 建立過程在其子類執行。

應用例項:

  1. 您需要一輛汽車,可以直接從工廠裡面提貨,而不用去管這輛汽車是怎麼做出來的,以及這個汽車裡面的具體實現。
  2. Hibernate 換資料庫只需換方言和驅動就可以。

優點:

  1. 一個呼叫者想建立一個物件,只要知道其名稱就可以了。
  2. 擴充套件性高,如果想增加一個產品,只要擴充套件一個工廠類就可以。
  3. 遮蔽產品的具體實現,呼叫者只關心產品的介面。

缺點: 每次增加一個產品時,都需要增加一個具體類和物件實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這並不是什麼好事。

使用場景:

  1. 日誌記錄器:記錄可能記錄到本地硬碟、系統事件、遠端伺服器等,使用者可以選擇記錄日誌到什麼地方。
  2. 資料庫訪問,當使用者不知道最後系統採用哪一類資料庫,以及資料庫可能有變化時。
  3. 設計一個連線伺服器的框架,需要三個協議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產品類,共同實現一個介面。

注意事項: 作為一種建立類模式,在任何需要生成複雜物件的地方,都可以使用工廠方法模式。有一點需要注意的地方就是複雜物件適合使用工廠模式,而簡單物件,特別是只需要通過 new 就可以完成建立的物件,無需使用工廠模式。如果使用工廠模式,就需要引入一個工廠類,會增加系統的複雜度。

抽象工廠模式

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠建立其他工廠。該超級工廠又稱為其他工廠的工廠。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
在抽象工廠模式中,介面是負責建立一個相關物件的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供物件。

介紹

意圖: 提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。

主要解決: 主要解決介面選擇的問題。

何時使用: 系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。

如何解決: 在一個產品族裡面,定義多個產品。

關鍵程式碼: 在一個工廠裡聚合多個同類產品。

應用例項: 工作了,為了參加一些聚會,肯定有兩套或多套衣服吧,比如說有商務裝(成套,一系列具體產品)、時尚裝(成套,一系列具體產品),甚至對於一個家庭來說,可能有商務女裝、商務男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產品。假設一種情況(現實中是不存在的,要不然,沒法進入共產主義了,但有利於說明抽象工廠模式),在您的家中,某一個衣櫃(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產品),每次拿這種成套的衣服時也自然要從這個衣櫃中取出了。用 OO 的思想去理解,所有的衣櫃(具體工廠)都是衣櫃類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產品),褲子(某一具體產品),這些具體的上衣其實也都是上衣(抽象產品),具體的褲子也都是褲子(另一個抽象產品)。

優點: 當一個產品族中的多個物件被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的物件。

缺點: 產品族擴充套件非常困難,要增加一個系列的某一產品,既要在抽象的 Creator 里加程式碼,又要在具體的裡面加程式碼。

使用場景:

  1. QQ 換皮膚,一整套一起換。
  2. 生成不同作業系統的程式。

注意事項: 產品族難擴充套件,產品等級易擴充套件。

單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意:

  • 1、單例類只能有一個例項。
  • 2、單例類必須自己建立自己的唯一例項。
  • 3、單例類必須給所有其他物件提供這一例項。

介紹

意圖: 保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

主要解決: 一個全域性使用的類頻繁地建立與銷燬。

何時使用: 當您想控制例項數目,節省系統資源的時候。

如何解決: 判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。

關鍵程式碼: 建構函式是私有的。

應用例項:

  1. 一個班級只有一個班主任。
  2. Windows 是多程式多執行緒的,在操作一個檔案的時候,就不可避免地出現多個程式或執行緒同時操作一個檔案的現象,所以所有檔案的處理必須通過唯一的例項來進行。
  3. 一些裝置管理器常常設計為單例模式,比如一個電腦有兩臺印表機,在輸出的時候就要處理不能兩臺印表機列印同一個檔案。

優點:

  1. 在記憶體裡只有一個例項,減少了記憶體的開銷,尤其是頻繁的建立和銷燬例項(比如管理學院首頁頁面快取)。
  2. 避免對資源的多重佔用(比如寫檔案操作)。

缺點: 沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

使用場景:

  1. 要求生產唯一序列號。
  2. WEB 中的計數器,不用每次重新整理都在資料庫里加一次,用單例先快取起來。
  3. 建立的一個物件需要消耗的資源過多,比如 I/O 與資料庫的連線等。

注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多執行緒同時進入造成 instance 被多次例項化。

建造者模式

建造者模式(Builder Pattern)使用多個簡單的物件一步一步構建成一個複雜的物件。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
一個 Builder 類會一步一步構造最終的物件。該 Builder 類是獨立於其他物件的。

介紹

意圖: 將一個複雜的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示。

主要解決: 主要解決在軟體系統中,有時候面臨著 "一個複雜物件" 的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定。

何時使用: 一些基本部件不會變,而其組合經常變化的時候。

如何解決: 將變與不變分離開。

關鍵程式碼: 建造者:建立和提供例項,導演:管理建造出來的例項的依賴關係。

應用例項:

  1. 去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,生成出所謂的 "套餐"。
  2. JAVA 中的 StringBuilder。

優點:

  1. 建造者獨立,易擴充套件。
  2. 便於控制細節風險。

缺點:

  1. 產品必須有共同點,範圍有限制。
  2. 如內部變化複雜,會有很多的建造類。

使用場景:

  1. 需要生成的物件具有複雜的內部結構。
  2. 需要生成的物件內部屬性本身相互依賴。

注意事項: 與工廠模式的區別是:建造者模式更加關注與零件裝配的順序。

原型模式

原型模式(Prototype Pattern)是用於建立重複的物件,同時又能保證效能。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。

介紹

意圖: 用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

主要解決: 在執行期建立和刪除原型。

何時使用:

  1. 當一個系統應該獨立於它的產品建立,構成和表示時。
  2. 當要例項化的類是在執行時刻指定時,例如,通過動態裝載。
  3. 為了避免建立一個與產品類層次平行的工廠類層次時。
  4. 當一個類的例項只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工例項化該類更方便一些。

如何解決: 利用已有的一個原型物件,快速地生成和原型物件一樣的例項。

關鍵程式碼:

  1. 實現克隆操作,在 JAVA 繼承 Cloneable,重寫 clone(),在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現物件的淺拷貝或通過序列化的方式來實現深拷貝。
  2. 原型模式同樣用於隔離類物件的使用者和具體型別(易變類)之間的耦合關係,它同樣要求這些 "易變類" 擁有穩定的介面。

應用例項:

  1. 細胞分裂。
  2. JAVA 中的 Object clone() 方法。

優點:

  1. 效能提高。
  2. 逃避建構函式的約束。

缺點:

  1. 配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支援序列化的間接物件,或者引用含有迴圈結構的時候。
  2. 必須實現 Cloneable 介面。

使用場景:

  1. 資源優化場景。
  2. 類初始化需要消化非常多的資源,這個資源包括資料. 硬體資源等。
  3. 效能和安全要求的場景。
  4. 通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
  5. 一個物件多個修改者的場景。
  6. 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。
  7. 在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法建立一個物件,然後由工廠方法提供給呼叫者。原型模式已經與 Java 融為渾然一體,大家可以隨手拿來使用。

注意事項: 與通過對一個類進行例項化來構造新物件不同的是,原型模式是通過拷貝一個現有物件生成新物件的。淺拷貝實現 Cloneable,重寫,深拷貝是通過實現 Serializable 讀取二進位制流。

物件池模式

物件池(也稱為資源池)被用來管理物件快取。物件池是一組已經初始化過且可以直接使用的物件集合,使用者在使用物件時可以從物件池中獲取物件,對其進行操作處理,並在不需要時歸還給物件池而非銷燬它。
若物件初始化、例項化的代價高,且需要經常例項化,但每次例項化的數量較少的情況下,使用物件池可以獲得顯著的效能提升。常見的使用物件池模式的技術包括執行緒池、資料庫連線池、任務佇列池、圖片資源物件池等。
當然,如果要例項化的物件較小,不需要多少資源開銷,就沒有必要使用物件池模式了,這非但不會提升效能,反而浪費記憶體空間,甚至降低效能。

示例程式碼

Pool.php

<?php

namespace DesignPatterns\Creational\Pool;

class Pool
{

    private $instances = array();
    private $class;

    public function __construct($class)
    {
        $this->class = $class;
    }

    public function get()
    {
        if (count($this->instances) > 0) {
            return array_pop($this->instances);
        }

        return new $this->class();
    }

    public function dispose($instance)
    {
        $this->instances[] = $instance;
    }
}

Processor.php

<?php

namespace DesignPatterns\Creational\Pool;

class Processor
{

    private $pool;
    private $processing = 0;
    private $maxProcesses = 3;
    private $waitingQueue = [];

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    public function process($image)
    {
        if ($this->processing++ < $this->maxProcesses) {
            $this->createWorker($image);
        } else {
            $this->pushToWaitingQueue($image);
        }
    }

    private function createWorker($image)
    {
        $worker = $this->pool->get();
        $worker->run($image, array($this, 'processDone'));
    }

    public function processDone($worker)
    {
        $this->processing--;
        $this->pool->dispose($worker);

        if (count($this->waitingQueue) > 0) {
            $this->createWorker($this->popFromWaitingQueue());
        }
    }

    private function pushToWaitingQueue($image)
    {
        $this->waitingQueue[] = $image;
    }

    private function popFromWaitingQueue()
    {
        return array_pop($this->waitingQueue);
    }
}

Worker.php

<?php

namespace DesignPatterns\Creational\Pool;

class Worker
{

    public function __construct()
    {
        // let's say that constuctor does really expensive work...
        // for example creates "thread"
    }

    public function run($image, array $callback)
    {
        // do something with $image...
        // and when it's done, execute callback
        call_user_func($callback, $this);
    }
}

多例模式

多例模式和單例模式類似,但可以返回多個例項。比如我們有多個資料庫連線,MySQL、SQLite、Postgres,又或者我們有多個日誌記錄器,分別用於記錄除錯資訊和錯誤資訊,這些都可以使用多例模式實現。

示例程式碼

Multiton.php

<?php

namespace DesignPatterns\Creational\Multiton;

/**
 * Multiton類
 */
class Multiton
{
    /**
     *
     * 第一個例項
     */
    const INSTANCE_1 = '1';

    /**
     *
     * 第二個例項
     */
    const INSTANCE_2 = '2';

    /**
     * 例項陣列
     *
     * @var array
     */
    private static $instances = array();

    /**
     * 建構函式是私有的,不能從外部進行例項化
     *
     */
    private function __construct()
    {
    }

    /**
     * 通過指定名稱返回例項(使用到該例項的時候才會例項化)
     *
     * @param string $instanceName
     *
     * @return Multiton
     */
    public static function getInstance($instanceName)
    {
        if (!array_key_exists($instanceName, self::$instances)) {
            self::$instances[$instanceName] = new self();
        }

        return self::$instances[$instanceName];
    }

    /**
     * 防止例項從外部被克隆
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * 防止例項從外部反序列化
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}

靜態工廠模式

與簡單工廠類似,該模式用於建立一組相關或依賴的物件,不同之處在於靜態工廠模式使用一個靜態方法來建立所有型別的物件,該靜態方法通常是 factory 或 build。

示例程式碼

StaticFactory.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

class StaticFactory
{
    /**
     * 通過傳入引數建立相應物件例項
     *
     * @param string $type
     *
     * @static
     *
     * @throws \InvalidArgumentException
     * @return FormatterInterface
     */
    public static function factory($type)
    {
        $className = __NAMESPACE__ . '\Format' . ucfirst($type);

        if (!class_exists($className)) {
            throw new \InvalidArgumentException('Missing format class.');
        }

        return new $className();
    }
}

FormatterInterface.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

/**
 * FormatterInterface介面
 */
interface FormatterInterface
{
}

FormatString.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

/**
 * FormatNumber類
 */
class FormatNumber implements FormatterInterface
{
}

介面卡模式

介面卡模式(Adapter Pattern)是作為兩個不相容的介面之間的橋樑。這種型別的設計模式屬於結構型模式,它結合了兩個獨立介面的功能。
這種模式涉及到一個單一的類,該類負責加入獨立的或不相容的介面功能。舉個真實的例子,讀卡器是作為記憶體卡和筆記本之間的介面卡。您將記憶體卡插入讀卡器,再將讀卡器插入筆記本,這樣就可以通過筆記本來讀取記憶體卡。

介紹

意圖: 將一個類的介面轉換成客戶希望的另外一個介面。介面卡模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。

主要解決: 主要解決在軟體系統中,常常要將一些 "現存的物件" 放到新的環境中,而新環境要求的介面是現物件不能滿足的。

何時使用:

  1. 系統需要使用現有的類,而此類的介面不符合系統的需要。
  2. 想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作,這些源類不一定有一致的介面。
  3. 通過介面轉換,將一個類插入另一個類系中。(比如老虎和飛禽,現在多了一個飛虎,在不增加實體的需求下,增加一個介面卡,在裡面包容一個虎物件,實現飛的介面。)

如何解決: 繼承或依賴(推薦)。

關鍵程式碼: 介面卡繼承或依賴已有的物件,實現想要的目標介面。

應用例項:

  1. 美國電器 110V,中國 220V,就要有一個介面卡將 110V 轉化為 220V。
  2. JAVA JDK 1.1 提供了 Enumeration 介面,而在 1.2 中提供了 Iterator 介面,想要使用 1.2 的 JDK,則要將以前系統的 Enumeration 介面轉化為 Iterator 介面,這時就需要介面卡模式。
  3. 在 LINUX 上執行 WINDOWS 程式。 4. JAVA 中的 jdbc。

優點:

  1. 可以讓任何兩個沒有關聯的類一起執行。
  2. 提高了類的複用。
  3. 增加了類的透明度。
  4. 靈活性好。

缺點:

  1. 過多地使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是 A 介面,其實內部被適配成了 B 介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。
  2. 由於 JAVA 至多繼承一個類,所以至多隻能適配一個適配者類,而且目標類必須是抽象類。

使用場景: 有動機地修改一個正常執行的系統的介面,這時應該考慮使用介面卡模式。

注意事項: 介面卡不是在詳細設計時新增的,而是解決正在服役的專案的問題。

橋接模式

橋接(Bridge)是用於把抽象化與實現化解耦,使得二者可以獨立變化。這種型別的設計模式屬於結構型模式,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。
這種模式涉及到一個作為橋接的介面,使得實體類的功能獨立於介面實現類。這兩種型別的類可被結構化改變而互不影響。

介紹

意圖: 將抽象部分與實現部分分離,使它們都可以獨立的變化。

主要解決: 在有多種可能會變化的情況下,用繼承會造成類爆炸問題,擴充套件起來不靈活。

何時使用: 實現系統可能有多個角度分類,每一種角度都可能變化。

如何解決: 把這種多角度分類分離出來,讓它們獨立變化,減少它們之間耦合。

關鍵程式碼: 抽象類依賴實現類。

應用例項:

  1. 豬八戒從天蓬元帥轉世投胎到豬,轉世投胎的機制將塵世劃分為兩個等級,即:靈魂和肉體,前者相當於抽象化,後者相當於實現化。生靈通過功能的委派,呼叫肉體物件的功能,使得生靈可以動態地選擇。
  2. 牆上的開關,可以看到的開關是抽象的,不用管裡面具體怎麼實現的。

優點:

  1. 抽象和實現的分離。
  2. 優秀的擴充套件能力。
  3. 實現細節對客戶透明。

缺點: 橋接模式的引入會增加系統的理解與設計難度,由於聚合關聯關係建立在抽象層,要求開發者針對抽象進行設計與程式設計。

使用場景:

  1. 如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯絡,通過橋接模式可以使它們在抽象層建立一個關聯關係。
  2. 對於那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。
  3. 一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件。

注意事項: 對於兩個獨立變化的維度,使用橋接模式再適合不過了。

過濾器模式

過濾器模式(Filter Pattern)或標準模式(Criteria Pattern)是一種設計模式,這種模式允許開發人員使用不同的標準來過濾一組物件,通過邏輯運算以解耦的方式把它們連線起來。這種型別的設計模式屬於結構型模式,它結合多個標準來獲得單一標準。

組合模式

組合模式(Composite Pattern),又叫部分整體模式,是用於把一組相似的物件當作一個單一的物件。組合模式依據樹形結構來組合物件,用來表示部分以及整體層次。這種型別的設計模式屬於結構型模式,它建立了物件組的樹形結構。
這種模式建立了一個包含自己物件組的類。該類提供了修改相同物件組的方式。

介紹

意圖: 將物件組合成樹形結構以表示 "部分 - 整體" 的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。

主要解決: 它在我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。

何時使用:

  1. 您想表示物件的部分 - 整體層次結構(樹形結構)。
  2. 您希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。

如何解決: 樹枝和葉子實現統一介面,樹枝內部組合該介面。

關鍵程式碼: 樹枝內部組合該介面,並且含有內部屬性 List,裡面放 Component。

應用例項:

  1. 算術表示式包括運算元. 操作符和另一個運算元,其中,另一個操作符也可以是運算元. 操作符和另一個運算元。
  2. 在 JAVA AWT 和 SWING 中,對於 Button 和 Checkbox 是樹葉,Container 是樹枝。

優點:

  1. 高層模組呼叫簡單。
  2. 節點自由增加。

缺點: 在使用組合模式時,其葉子和樹枝的宣告都是實現類,而不是介面,違反了依賴倒置原則。

使用場景: 部分. 整體場景,如樹形選單,檔案. 資料夾的管理。

注意事項: 定義時為具體類。

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是作為現有的類的一個包裝。
這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。

介紹

意圖: 動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。

主要解決: 一般的,我們為了擴充套件一個類經常使用繼承方式實現,由於繼承為類引入靜態特徵,並且隨著擴充套件功能的增多,子類會很膨脹。

何時使用: 在不想增加很多子類的情況下擴充套件類。

如何解決: 將具體功能職責劃分,同時繼承裝飾者模式。

關鍵程式碼:

  1. Component 類充當抽象角色,不應該具體實現。
  2. 修飾類引用和繼承 Component 類,具體擴充套件類重寫父類方法。

應用例項:

  1. 孫悟空有 72 變,當他變成 "廟宇" 後,他的根本還是一隻猴子,但是他又有了廟宇的功能。
  2. 不論一幅畫有沒有畫框都可以掛在牆上,但是通常都是有畫框的,並且實際上是畫框被掛在牆上。在掛在牆上之前,畫可以被蒙上玻璃,裝到框子裡;這時畫、玻璃和畫框形成了一個物體。

優點: 裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。

缺點: 多層裝飾比較複雜。

使用場景:

  1. 擴充套件一個類的功能。
  2. 動態增加功能,動態撤銷。

注意事項: 可代替繼承。

外觀模式

外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的介面。這種型別的設計模式屬於結構型模式,它向現有的系統新增一個介面,來隱藏系統的複雜性。
這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委託呼叫。

介紹

意圖: 為子系統中的一組介面提供一個一致的介面,外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。

主要解決: 降低訪問複雜系統的內部子系統時的複雜度,簡化客戶端與之的介面。

何時使用:

  1. 客戶端不需要知道系統內部的複雜聯絡,整個系統只需提供一個 "接待員" 即可。
  2. 定義系統的入口。

如何解決: 客戶端不與系統耦合,外觀類與系統耦合。

關鍵程式碼: 在客戶端和複雜系統之間再加一層,這一層將呼叫順序. 依賴關係等處理好。

應用例項:

  1. 去醫院看病,可能要去掛號、門診、劃價、取藥,讓患者或患者家屬覺得很複雜,如果有提供接待人員,只讓接待人員來處理,就很方便。
  2. JAVA 的三層開發模式。

優點:

  1. 減少系統相互依賴。
  2. 提高靈活性。
  3. 提高了安全性。

缺點: 不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。

使用場景:

  1. 為複雜的模組或子系統提供外界訪問的模組。
  2. 子系統相對獨立。
  3. 預防低水平人員帶來的風險。

注意事項: 在層次化結構中,可以使用外觀模式定義系統中每一層的入口。

享元模式

享元模式(Flyweight Pattern)主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式。
享元模式嘗試重用現有的同類物件,如果未找到匹配的物件,則建立新物件。我們將通過建立 5 個物件來畫出 20 個分佈於不同位置的圓來演示這種模式。由於只有 5 種可用的顏色,所以 color 屬性被用來檢查現有的 Circle 物件。

介紹

意圖: 運用共享技術有效地支援大量細粒度的物件。

主要解決: 在有大量物件時,有可能會造成記憶體溢位,我們把其中共同的部分抽象出來,如果有相同的業務請求,直接返回在記憶體中已有的物件,避免重新建立。

何時使用:

  1. 系統中有大量物件。
  2. 這些物件消耗大量記憶體。
  3. 這些物件的狀態大部分可以外部化。
  4. 這些物件可以按照內蘊狀態分為很多組,當把外蘊物件從物件中剔除出來時,每一組物件都可以用一個物件來代替。
  5. 系統不依賴於這些物件身份,這些物件是不可分辨的。

如何解決: 用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識碼所標識的物件。

關鍵程式碼: 用 HashMap 儲存這些物件。

應用例項:

  1. JAVA 中的 String,如果有則返回,如果沒有則建立一個字串儲存在字串快取池裡面。2. 資料庫的資料池。

優點: 大大減少物件的建立,降低系統的記憶體,使效率提高。

缺點: 提高了系統的複雜度,需要分離出外部狀態和內部狀態,而且外部狀態具有固有化的性質,不應該隨著內部狀態的變化而變化,否則會造成系統的混亂。

使用場景:

  1. 系統有大量相似物件。
  2. 需要緩衝池的場景。

注意事項:

  1. 注意劃分外部狀態和內部狀態,否則可能會引起執行緒安全問題。
  2. 這些類必須有一個工廠物件加以控制。

代理模式

在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種型別的設計模式屬於結構型模式。
在代理模式中,我們建立具有現有物件的物件,以便向外界提供功能介面。

介紹

意圖: 為其他物件提供一種代理以控制對這個物件的訪問。

主要解決: 在直接訪問物件時帶來的問題,比如說:要訪問的物件在遠端的機器上。在物件導向系統中,有些物件由於某些原因(比如物件建立開銷很大,或者某些操作需要安全控制,或者需要程式外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此物件時加上一個對此物件的訪問層。

何時使用: 想在訪問一個類時做一些控制。

如何解決: 增加中間層。

關鍵程式碼: 實現與被代理類組合。

應用例項:

  1. Windows 裡面的快捷方式。
  2. 豬八戒去找高翠蘭結果是孫悟空變的,可以這樣理解:把高翠蘭的外貌抽象出來,高翠蘭本人和孫悟空都實現了這個介面,豬八戒訪問高翠蘭的時候看不出來這個是孫悟空,所以說孫悟空是高翠蘭代理類。
  3. 買火車票不一定在火車站買,也可以去代售點。
  4. 一張支票或銀行存單是賬戶中資金的代理。支票在市場交易中用來代替現金,並提供對簽發人賬號上資金的控制。
  5. spring aop。

優點:

  1. 職責清晰。
  2. 高擴充套件性。
  3. 智慧化。

缺點:

  1. 由於在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢。
  2. 實現代理模式需要額外的工作,有些代理模式的實現非常複雜。

使用場景: 按職責來劃分,通常有以下使用場景:

  1. 遠端代理。
  2. 虛擬代理。
  3. Copy-on-Write 代理。
  4. 保護(Protect or Access)代理。
  5. Cache 代理。
  6. 防火牆(Firewall)代理。
  7. 同步化(Synchronization)代理。
  8. 智慧引用(Smart Reference)代理。

注意事項:

  1. 和介面卡模式的區別:介面卡模式主要改變所考慮物件的介面,而代理模式不能改變所代理類的介面。
  2. 和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制。

資料對映模式

在瞭解資料對映模式之前,先了解下資料對映,它是在持久化資料儲存層(通常是關係型資料庫)和駐於記憶體的資料表現層之間進行雙向資料傳輸的資料訪問層。
資料對映模式的目的是讓持久化資料儲存層、駐於記憶體的資料表現層、以及資料對映本身三者相互獨立、互不依賴。這個資料訪問層由一個或多個對映器(或者資料訪問物件)組成,用於實現資料傳輸。通用的資料訪問層可以處理不同的實體型別,而專用的則處理一個或幾個。
資料對映模式的核心在於它的資料模型遵循單一職責原則(Single Responsibility Principle), 這也是和 Active Record 模式的不同之處。最典型的資料對映模式例子就是資料庫 ORM 模型 (Object Relational Mapper)。
準確來說該模式是個架構模式。

依賴注入模式

依賴注入(Dependency Injection)是控制反轉(Inversion of Control)的一種實現方式。
我們先來看看什麼是控制反轉。
當呼叫者需要被呼叫者的協助時,在傳統的程式設計過程中,通常由呼叫者來建立被呼叫者的例項,但在這裡,建立被呼叫者的工作不再由呼叫者來完成,而是將被呼叫者的建立移到呼叫者的外部,從而反轉被呼叫者的建立,消除了呼叫者對被呼叫者建立的控制,因此稱為控制反轉。
要實現控制反轉,通常的解決方案是將建立被呼叫者例項的工作交由 IoC 容器來完成,然後在呼叫者中注入被呼叫者(通過構造器/方法注入實現),這樣我們就實現了呼叫者與被呼叫者的解耦,該過程被稱為依賴注入。
依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開發出鬆散耦合(loose coupled)、可維護、可測試的程式碼和程式。這條原則的做法是大家熟知的面向介面,或者說是面向抽象程式設計。

門面模式

門面模式(Facade)又稱外觀模式,用於為子系統中的一組介面提供一個一致的介面。門面模式定義了一個高層介面,這個介面使得子系統更加容易使用:引入門面角色之後,使用者只需要直接與門面角色互動,使用者與子系統之間的複雜關係由門面角色來實現,從而降低了系統的耦合度。

示例程式碼

Facade.php

<?php

namespace DesignPatterns\Structural\Facade;

/**
 * 門面類
 */
class Facade
{
    /**
     * @var OsInterface
     */
    protected $os;

    /**
     * @var BiosInterface
     */
    protected $bios;

    /**
     * This is the perfect time to use a dependency injection container
     * to create an instance of this class
     *
     * @param BiosInterface $bios
     * @param OsInterface   $os
     */
    public function __construct(BiosInterface $bios, OsInterface $os)
    {
        $this->bios = $bios;
        $this->os = $os;
    }

    /**
     * turn on the system
     */
    public function turnOn()
    {
        $this->bios->execute();
        $this->bios->waitForKeyPress();
        $this->bios->launch($this->os);
    }

    /**
     * turn off the system
     */
    public function turnOff()
    {
        $this->os->halt();
        $this->bios->powerDown();
    }
}

OsInterface.php

<?php

namespace DesignPatterns\Structural\Facade;

/**
 * OsInterface介面
 */
interface OsInterface
{
    /**
     * halt the OS
     */
    public function halt();
}

BiosInterface.php

<?php

namespace DesignPatterns\Structural\Facade;

/**
 * BiosInterface介面
 */
interface BiosInterface
{
    /**
     * execute the BIOS
     */
    public function execute();

    /**
     * wait for halt
     */
    public function waitForKeyPress();

    /**
     * launches the OS
     *
     * @param OsInterface $os
     */
    public function launch(OsInterface $os);

    /**
     * power down BIOS
     */
    public function powerDown();
}

流介面模式

在軟體工程中,流介面(Fluent Interface)是指實現一種物件導向的、能提高程式碼可讀性的 API 的方法,其目的就是可以編寫具有自然語言一樣可讀性的程式碼,我們對這種程式碼編寫方式還有一個通俗的稱呼 —— 方法鏈。
Laravel 中流介面模式有著廣泛使用,比如查詢構建器,郵件等等。

示例程式碼

Sql.php

<?php

namespace DesignPatterns\Structural\FluentInterface;

/**
 * SQL 類
 */
class Sql
{
    /**
     * @var array
     */
    protected $fields = array();

    /**
     * @var array
     */
    protected $from = array();

    /**
     * @var array
     */
    protected $where = array();

    /**
     * 新增 select 欄位
     *
     * @param array $fields
     *
     * @return SQL
     */
    public function select(array $fields = array())
    {
        $this->fields = $fields;

        return $this;
    }

    /**
     * 新增 FROM 子句
     *
     * @param string $table
     * @param string $alias
     *
     * @return SQL
     */
    public function from($table, $alias)
    {
        $this->from[] = $table . ' AS ' . $alias;

        return $this;
    }

    /**
     * 新增 WHERE 條件
     *
     * @param string $condition
     *
     * @return SQL
     */
    public function where($condition)
    {
        $this->where[] = $condition;

        return $this;
    }

    /**
     * 生成查詢語句
     *
     * @return string
     */
    public function getQuery()
    {
        return 'SELECT ' . implode(',', $this->fields)
                . ' FROM ' . implode(',', $this->from)
                . ' WHERE ' . implode(' AND ', $this->where);
    }
}

註冊模式

註冊模式(Registry)也叫做註冊樹模式,註冊器模式。註冊模式為應用中經常使用的物件建立一箇中央儲存器來存放這些物件 —— 通常通過一個只包含靜態方法的抽象類來實現(或者通過單例模式)。

示例程式碼

Registry.php

<?php

namespace DesignPatterns\Structural\Registry;

/**
 * class Registry
 */
abstract class Registry
{
    const LOGGER = 'logger';

    /**
     * @var array
     */
    protected static $storedValues = array();

    /**
     * sets a value
     *
     * @param string $key
     * @param mixed  $value
     *
     * @static
     * @return void
     */
    public static function set($key, $value)
    {
        self::$storedValues[$key] = $value;
    }

    /**
     * gets a value from the registry
     *
     * @param string $key
     *
     * @static
     * @return mixed
     */
    public static function get($key)
    {
        return self::$storedValues[$key];
    }

    // typically there would be methods to check if a key has already been registered and so on ...
}

責任鏈模式

顧名思義,責任鏈模式(Chain of Responsibility Pattern)為請求建立了一個接收者物件的鏈。這種模式給予請求的型別,對請求的傳送者和接收者進行解耦。這種型別的設計模式屬於行為型模式。
在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個物件不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

介紹

意圖: 避免請求傳送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,直到有物件處理它為止。

主要解決: 職責鏈上的處理者負責處理請求,客戶只需要將請求傳送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的傳送者和請求的處理者解耦了。

何時使用: 在處理訊息的時候以過濾很多道。

如何解決: 攔截的類都實現統一介面。

關鍵程式碼: Handler 裡面聚合它自己,在 HandlerRequest 裡判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。

應用例項:

  1. 紅樓夢中的 "擊鼓傳花"。
  2. JS 中的事件冒泡。
  3. JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。

優點:

  1. 降低耦合度。它將請求的傳送者和接收者解耦。
  2. 簡化了物件。使得物件不需要知道鏈的結構。
  3. 增強給物件指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。
  4. 增加新的請求處理類很方便。

缺點:

  1. 不能保證請求一定被接收。
  2. 系統效能將受到一定影響,而且在進行程式碼除錯時不太方便,可能會造成迴圈呼叫。
  3. 可能不容易觀察執行時的特徵,有礙於除錯。

使用場景:

  1. 有多個物件可以處理同一個請求,具體哪個物件處理該請求由執行時刻自動確定。
  2. 在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。
  3. 可動態指定一組物件處理請求。

注意事項: 在 JAVA WEB 中遇到很多應用。

命令模式

命令模式(Command Pattern)是一種資料驅動的設計模式,它屬於行為型模式。請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令。

介紹

意圖: 將一個請求封裝成一個物件,從而使您可以用不同的請求對客戶進行引數化。

主要解決: 在軟體系統中,行為請求者與行為實現者通常是一種緊耦合的關係,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適。

何時使用: 在某些場合,比如要對行為進行 "記錄、撤銷 / 重做、事務" 等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將 "行為請求者" 與 "行為實現者" 解耦?將一組行為抽象為物件,可以實現二者之間的鬆耦合。

如何解決: 通過呼叫者呼叫接受者執行命令,順序:呼叫者→接受者→命令。

關鍵程式碼: 定義三個角色:

  1. received 真正的命令執行物件
  2. Command
  3. invoker 使用命令物件的入口

應用例項: struts 1 中的 action 核心控制器 ActionServlet 只有一個,相當於 Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當於具體的 Command。

優點:

  1. 降低了系統耦合度。
  2. 新的命令可以很容易新增到系統中去。

缺點: 使用命令模式可能會導致某些系統有過多的具體命令類。

使用場景: 認為是命令的地方都可以使用命令模式,比如:

  1. GUI 中每一個按鈕都是一條命令。
  2. 模擬 CMD。

注意事項: 系統需要支援命令的撤銷 (Undo) 操作和恢復 (Redo) 操作,也可以考慮使用命令模式,見命令模式的擴充套件。

直譯器模式

直譯器模式(Interpreter Pattern)提供了評估語言的語法或表示式的方式,它屬於行為型模式。這種模式實現了一個表示式介面,該介面解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。

介紹

意圖: 給定一個語言,定義它的文法表示,並定義一個直譯器,這個直譯器使用該標識來解釋語言中的句子。

主要解決: 對於一些固定文法構建一個解釋句子的直譯器。

何時使用: 如果一種特定型別的問題發生的頻率足夠高,那麼可能就值得將該問題的各個例項表述為一個簡單語言中的句子。這樣就可以構建一個直譯器,該直譯器通過解釋這些句子來解決該問題。

如何解決: 構建語法樹,定義終結符與非終結符。

關鍵程式碼: 構建環境類,包含直譯器之外的一些全域性資訊,一般是 HashMap。

應用例項: 編譯器、運算表示式計算。

優點:

  1. 可擴充套件性比較好,靈活。
  2. 增加了新的解釋表示式的方式。
  3. 易於實現簡單文法。

缺點:

  1. 可利用場景比較少。
  2. 對於複雜的文法比較難維護。
  3. 直譯器模式會引起類膨脹。
  4. 直譯器模式採用遞迴呼叫方法。

使用場景:

  1. 可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹。
  2. 一些重複出現的問題可以用一種簡單的語言來進行表達。
  3. 一個簡單語法需要解釋的場景。

注意事項: 可利用場景比較少,JAVA 中如果碰到可以用 expression4J 代替。

迭代器模式

迭代器模式(Iterator Pattern)是 Java 和 .Net 程式設計環境中非常常用的設計模式。這種模式用於順序訪問集合物件的元素,不需要知道集合物件的底層表示。
迭代器模式屬於行為型模式。

介紹

意圖: 提供一種方法順序訪問一個聚合物件中各個元素, 而又無須暴露該物件的內部表示。

主要解決: 不同的方式來遍歷整個整合物件。

何時使用: 遍歷一個聚合物件。

如何解決: 把在元素之間遊走的責任交給迭代器,而不是聚合物件。

關鍵程式碼: 定義介面:hasNext, next。

應用例項: JAVA 中的 iterator。

優點:

  1. 它支援以不同的方式遍歷一個聚合物件。
  2. 迭代器簡化了聚合類。
  3. 在同一個聚合上可以有多個遍歷。
  4. 在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有程式碼。

缺點: 由於迭代器模式將儲存資料和遍歷資料的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的複雜性。

使用場景:

  1. 訪問一個聚合物件的內容而無須暴露它的內部表示。
  2. 需要為聚合物件提供多種遍歷方式。
  3. 為遍歷不同的聚合結構提供一個統一的介面。

注意事項: 迭代器模式就是分離了集合物件的遍歷行為,抽象出一個迭代器類來負責,這樣既可以做到不暴露集合的內部結構,又可讓外部程式碼透明地訪問集合內部的資料。

中介者模式

中介者模式(Mediator Pattern)是用來降低多個物件和類之間的通訊複雜性。這種模式提供了一箇中介類,該類通常處理不同類之間的通訊,並支援鬆耦合,使程式碼易於維護。中介者模式屬於行為型模式。

介紹

意圖: 用一箇中介物件來封裝一系列的物件互動,中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。

主要解決: 物件與物件之間存在大量的關聯關係,這樣勢必會導致系統的結構變得很複雜,同時若一個物件發生改變,我們也需要跟蹤與之相關聯的物件,同時做出相應的處理。

何時使用: 多個類相互耦合,形成了網狀結構。

如何解決: 將上述網狀結構分離為星型結構。

關鍵程式碼: 物件 Colleague 之間的通訊封裝到一個類中單獨處理。

應用例項:

  1. 中國加入 WTO 之前是各個國家相互貿易,結構複雜,現在是各個國家通過 WTO 來互相貿易。
  2. 機場排程系統。
  3. MVC 框架,其中 C(控制器)就是 M(模型)和 V(檢視)的中介者。

優點:

  1. 降低了類的複雜度,將一對多轉化成了一對一。
  2. 各個類之間的解耦。
  3. 符合迪米特原則。

缺點: 中介者會龐大,變得複雜難以維護。

使用場景:

  1. 系統中物件之間存在比較複雜的引用關係,導致它們之間的依賴關係結構混亂而且難以複用該物件。
  2. 想通過一箇中間類來封裝多個類中的行為,而又不想生成太多的子類。

注意事項: 不應當在職責混亂的時候使用。

備忘錄模式

備忘錄模式(Memento Pattern)儲存一個物件的某個狀態,以便在適當的時候恢復物件。備忘錄模式屬於行為型模式。

介紹

意圖: 在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。

主要解決: 所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。

何時使用: 很多時候我們總是需要記錄一個物件的內部狀態,這樣做的目的就是為了允許使用者取消不確定或者錯誤的操作,能夠恢復到他原先的狀態,使得他有 "後悔藥" 可吃。

如何解決: 通過一個備忘錄類專門儲存物件狀態。

關鍵程式碼: 客戶不與備忘錄類耦合,與備忘錄管理類耦合。

應用例項:

  1. 後悔藥。
  2. 打遊戲時的存檔。
  3. Windows 裡的 ctri + z。
  4. IE 中的後退。
  5. 資料庫的事務管理。

優點:

  1. 給使用者提供了一種可以恢復狀態的機制,可以使使用者能夠比較方便地回到某個歷史的狀態。
  2. 實現了資訊的封裝,使得使用者不需要關心狀態的儲存細節。

缺點: 消耗資源。如果類的成員變數過多,勢必會佔用比較大的資源,而且每一次儲存都會消耗一定的記憶體。

使用場景:

  1. 需要儲存 / 恢復資料的相關狀態場景。
  2. 提供一個可回滾的操作。

注意事項:

  1. 為了符合迪米特原則,還要增加一個管理備忘錄的類。
  2. 為了節約記憶體,可使用原型模式 + 備忘錄模式。

觀察者模式

當物件間存在一對多關係時,則使用觀察者模式(Observer Pattern)。比如,當一個物件被修改時,則會自動通知它的依賴物件。觀察者模式屬於行為型模式。

介紹

意圖: 定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。

主要解決: 一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作。

何時使用: 一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。

如何解決: 使用物件導向技術,可以將這種依賴關係弱化。

關鍵程式碼: 在抽象類裡有一個 ArrayList 存放觀察者們。

應用例項:

  1. 拍賣的時候,拍賣師觀察最高標價,然後通知給其他競價者競價。
  2. 西遊記裡面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動作。

優點:

  1. 觀察者和被觀察者是抽象耦合的。
  2. 建立一套觸發機制。

缺點:

  1. 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
  2. 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

使用場景:

  1. 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的物件中使它們可以各自獨立地改變和複用。
  2. 一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度。
  3. 一個物件必須通知其他物件,而並不知道這些物件是誰。
  4. 需要在系統中建立一個觸發鏈,A 物件的行為將影響 B 物件,B 物件的行為將影響 C 物件……,可以使用觀察者模式建立一種鏈式觸發機制。

注意事項:

  1. JAVA 中已經有了對觀察者模式的支援類。
  2. 避免迴圈引用。
  3. 如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式。

狀態模式

在狀態模式(State Pattern)中,類的行為是基於它的狀態改變的。這種型別的設計模式屬於行為型模式。
在狀態模式中,我們建立表示各種狀態的物件和一個行為隨著狀態物件改變而改變的 context 物件。

介紹

意圖: 允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類。

主要解決: 物件的行為依賴於它的狀態(屬性),並且可以根據它的狀態改變而改變它的相關行為。

何時使用: 程式碼中包含大量與物件狀態有關的條件語句。

如何解決: 將各種具體的狀態類抽象出來。

關鍵程式碼: 通常命令模式的介面中只有一個方法。而狀態模式的介面中有一個或者多個方法。而且,狀態模式的實現類的方法,一般返回值,或者是改變例項變數的值。也就是說,狀態模式一般和物件的狀態有關。實現類的方法有不同的功能,覆蓋介面中的方法。狀態模式和命令模式一樣,也可以用於消除 if...else 等條件選擇語句。

應用例項:

  1. 打籃球的時候運動員可以有正常狀態. 不正常狀態和超常狀態。
  2. 曾侯乙編鐘中,'鍾是抽象介面','鍾 A'等是具體狀態,'曾侯乙編鐘'是具體環境(Context)。

優點:

  1. 封裝了轉換規則。
  2. 列舉可能的狀態,在列舉狀態之前需要確定狀態種類。
  3. 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為。
  4. 允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件語句塊。
  5. 可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。

缺點:

  1. 狀態模式的使用必然會增加系統類和物件的個數。
  2. 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂。
  3. 狀態模式對 "開閉原則" 的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的原始碼。

使用場景:

  1. 行為隨狀態改變而改變的場景。
  2. 條件、分支語句的代替者。

注意事項: 在行為受狀態約束的時候使用狀態模式,而且狀態不超過 5 個。

空物件模式

在空物件模式(Null Object Pattern)中,一個空物件取代 NULL 物件例項的檢查。Null 物件不是檢查空值,而是反應一個不做任何動作的關係。這樣的 Null 物件也可以在資料不可用的時候提供預設的行為。
在空物件模式中,我們建立一個指定各種要執行的操作的抽象類和擴充套件該類的實體類,還建立一個未對該類做任何實現的空物件類,該空物件類將無縫地使用在需要檢查空值的地方。

策略模式

在策略模式(Strategy Pattern)中,一個類的行為或其演算法可以在執行時更改。這種型別的設計模式屬於行為型模式。
在策略模式中,我們建立表示各種策略的物件和一個行為隨著策略物件改變而改變的 context 物件。策略物件改變 context 物件的執行演算法。

介紹

意圖: 定義一系列的演算法, 把它們一個個封裝起來, 並且使它們可相互替換。

主要解決: 在有多種演算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。

何時使用: 一個系統有許多許多類,而區分它們的只是他們直接的行為。

如何解決: 將這些演算法封裝成一個一個的類,任意地替換。

關鍵程式碼: 實現同一個介面。

應用例項:

  1. 諸葛亮的錦囊妙計,每一個錦囊就是一個策略。
  2. 旅行的出遊方式,選擇騎自行車. 坐汽車,每一種旅行方式都是一個策略。
  3. JAVA AWT 中的 LayoutManager。

優點:

  1. 演算法可以自由切換。
  2. 避免使用多重條件判斷。
  3. 擴充套件性良好。

缺點:

  1. 策略類會增多。
  2. 所有策略類都需要對外暴露。

使用場景:

  1. 如果在一個系統裡面有許多類,它們之間的區別僅在於它們的行為,那麼使用策略模式可以動態地讓一個物件在許多行為中選擇一種行為。
  2. 一個系統需要動態地在幾種演算法中選擇一種。
  3. 如果一個物件有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語句來實現。

注意事項: 如果一個系統的策略多於四個,就需要考慮使用混合模式,解決策略類膨脹的問題。

模板模式

在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式 / 模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。

介紹

意圖: 定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

主要解決: 一些方法通用,卻在每一個子類都重新寫了這一方法。

何時使用: 有一些通用的方法。

如何解決: 將這些通用演算法抽象出來。

關鍵程式碼: 在抽象類實現,其他步驟在子類實現。

應用例項:

  1. 在造房子的時候,地基、走線、水管都一樣,只有在建築的後期才有加壁櫥加柵欄等差異。 2. 西遊記裡面菩薩定好的 81 難,這就是一個頂層的邏輯骨架。
  2. spring 中對 Hibernate 的支援,將一些已經定好的方法封裝起來,比如開啟事務. 獲取 Session. 關閉 Session 等,程式設計師不重複寫那些已經規範好的程式碼,直接丟一個實體就可以儲存。

優點:

  1. 封裝不變部分,擴充套件可變部分。
  2. 提取公共程式碼,便於維護。
  3. 行為由父類控制,子類實現。

缺點: 每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。

使用場景:

  1. 有多個子類共有的方法,且邏輯相同。
  2. 重要的、複雜的方法,可以考慮作為模板方法。

注意事項: 為防止惡意操作,一般模板方法都加上 final 關鍵詞。

訪問者模式

在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執行演算法。通過這種方式,元素的執行演算法可以隨著訪問者改變而改變。這種型別的設計模式屬於行為型模式。根據模式,元素物件已接受訪問者物件,這樣訪問者物件就可以處理元素物件上的操作。

介紹

意圖: 主要將資料結構與資料操作分離。

主要解決: 穩定的資料結構和易變的操作耦合問題。

何時使用: 需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而需要避免讓這些操作 "汙染" 這些物件的類,使用訪問者模式將這些封裝到類中。

如何解決: 在被訪問的類裡面加一個對外提供接待訪問者的介面。

關鍵程式碼: 在資料基礎類裡面有一個方法接受訪問者,將自身引用傳入訪問者。

應用例項: 您在朋友家做客,您是訪問者,朋友接受您的訪問,您通過朋友的描述,然後對朋友的描述做出一個判斷,這就是訪問者模式。

優點:

  1. 符合單一職責原則。
  2. 優秀的擴充套件性。
  3. 靈活性。

缺點:

  1. 具體元素對訪問者公佈細節,違反了迪米特原則。
  2. 具體元素變更比較困難。
  3. 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。

使用場景:

  1. 物件結構中物件對應的類很少改變,但經常需要在此物件結構上定義新的操作。
  2. 需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而需要避免讓這些操作 "汙染" 這些物件的類,也不希望在增加新操作時修改這些類。

注意事項: 訪問者可以對功能進行統一,可以做報表、UI、攔截器與過濾器。

規格模式

規格模式(Specification)可以認為是組合模式的一種擴充套件。有時專案中某些條件決定了業務邏輯,這些條件就可以抽離出來以某種關係(與、或、非)進行組合,從而靈活地對業務邏輯進行定製。另外,在查詢、過濾等應用場合中,通過預定義多個條件,然後使用這些條件的組合來處理查詢或過濾,而不是使用邏輯判斷語句來處理,可以簡化整個實現邏輯。
這裡的每個條件就是一個規格,多個規格/條件通過串聯的方式以某種邏輯關係形成一個組合式的規格。

訪問者模式

我們去銀行櫃檯辦業務,一般情況下會開幾個個人業務櫃檯的,你去其中任何一個櫃檯辦理都是可以的。我們的訪問者模式可以很好付諸在這個場景中:對於銀行櫃檯來說,他們是不用變化的,就是說今天和明天提供個人業務的櫃檯是不需要有變化的。而我們作為訪問者,今天來銀行可能是取消費流水,明天來銀行可能是去辦理手機銀行業務,這些是我們訪問者的操作,一直是在變化的。
訪問者模式就是表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。


MVC模式

MVC 模式代表 Model-View-Controller(模型 - 檢視 - 控制器) 模式。這種模式用於應用程式的分層開發。

  • Model(模型) - 模型代表一個存取資料的物件或 JAVA POJO。它也可以帶有邏輯,在資料變化時更新控制器。
  • View(檢視) - 檢視代表模型包含的資料的視覺化。
  • Controller(控制器) - 控制器作用於模型和檢視上。它控制資料流向模型物件,並在資料變化時更新檢視。它使檢視與模型分離開。

業務代表模式

業務代表模式(Business Delegate Pattern)用於對錶示層和業務層解耦。它基本上是用來減少通訊或對錶示層程式碼中的業務層程式碼的遠端查詢功能。在業務層中我們有以下實體。

  • 客戶端(Client) - 表示層程式碼可以是 JSP、servlet 或 UI java 程式碼。
  • 業務代表(Business Delegate) - 一個為客戶端實體提供的入口類,它提供了對業務服務方法的訪問。
  • 查詢服務(LookUp Service) - 查詢服務物件負責獲取相關的業務實現,並提供業務物件對業務代表物件的訪問。
  • 業務服務(Business Service) - 業務服務介面。實現了該業務服務的實體類,提供了實際的業務實現邏輯。

組合實體模式

組合實體模式(Composite Entity Pattern)用在 EJB 持久化機制中。一個組合實體是一個 EJB 實體 bean,代表了物件的圖解。當更新一個組合實體時,內部依賴物件 beans 會自動更新,因為它們是由 EJB 實體 bean 管理的。以下是組合實體 bean 的參與者。

  • 組合實體(Composite Entity) - 它是主要的實體 bean。它可以是粗粒的,或者可以包含一個粗粒度物件,用於持續生命週期。
  • 粗粒度物件(Coarse-Grained Object) - 該物件包含依賴物件。它有自己的生命週期,也能管理依賴物件的生命週期。
  • 依賴物件(Dependent Object) - 依賴物件是一個持續生命週期依賴於粗粒度物件的物件。
  • 策略(Strategies) - 策略表示如何實現組合實體。

資料訪問物件模式

資料訪問物件模式(Data Access Object Pattern)或 DAO 模式用於把低階的資料訪問 API 或操作從高階的業務服務中分離出來。以下是資料訪問物件模式的參與者。

  • 資料訪問物件介面(Data Access Object Interface) - 該介面定義了在一個模型物件上要執行的標準操作。
  • 資料訪問物件實體類(Data Access Object concrete class) - 該類實現了上述的介面。該類負責從資料來源獲取資料,資料來源可以是資料庫,也可以是 xml,或者是其他的儲存機制。
  • 模型物件 / 數值物件(Model Object/Value Object) - 該物件是簡單的 POJO,包含了 get/set 方法來儲存通過使用 DAO 類檢索到的資料。

前端控制器模式

前端控制器模式(Front Controller Pattern)是用來提供一個集中的請求處理機制,所有的請求都將由一個單一的處理程式處理。該處理程式可以做認證 / 授權 / 記錄日誌,或者跟蹤請求,然後把請求傳給相應的處理程式。以下是這種設計模式的實體。

  • 前端控制器(Front Controller) - 處理應用程式所有型別請求的單個處理程式,應用程式可以是基於 web 的應用程式,也可以是基於桌面的應用程式。
  • 排程器(Dispatcher) - 前端控制器可能使用一個排程器物件來排程請求到相應的具體處理程式。
  • 檢視(View) - 檢視是為請求而建立的物件。

攔截過濾器模式

攔截過濾器模式(Intercepting Filter Pattern)用於對應用程式的請求或響應做一些預處理 / 後處理。定義過濾器,並在把請求傳給實際目標應用程式之前應用在請求上。過濾器可以做認證 / 授權 / 記錄日誌,或者跟蹤請求,然後把請求傳給相應的處理程式。以下是這種設計模式的實體。

  • 過濾器(Filter) - 過濾器在請求處理程式執行請求之前或之後,執行某些任務。
  • 過濾器鏈(Filter Chain) - 過濾器鏈帶有多個過濾器,並在 Target 上按照定義的順序執行這些過濾器。
  • Target - Target 物件是請求處理程式。
  • 過濾管理器(Filter Manager) - 過濾管理器管理過濾器和過濾器鏈。
  • 客戶端(Client) - Client 是向 Target 物件傳送請求的物件。

服務定位器模式

服務定位器模式(Service Locator Pattern)用在我們想使用 JNDI 查詢定位各種服務的時候。考慮到為某個服務查詢 JNDI 的代價很高,服務定位器模式充分利用了快取技術。在首次請求某個服務時,服務定位器在 JNDI 中查詢服務,並快取該服務物件。當再次請求相同的服務時,服務定位器會在它的快取中查詢,這樣可以在很大程度上提高應用程式的效能。以下是這種設計模式的實體。

當系統中的元件需要呼叫某一服務來完成特定的任務時,通常最簡單的做法是使用 new 關鍵字來建立該服務的例項,或者通過工廠模式來解耦該元件與服務的具體實現部分,以便通過配置資訊等更為靈活的方式獲得該服務的例項。然而,這些做法都有著各自的弊端:

  • 在元件中直接維護對服務例項的引用,會造成元件與服務之間的關聯依賴,當需要替換服務的具體實現時,不得不修改元件中呼叫服務的部分並重新編譯解決方案;即使採用工廠模式來根據配置資訊動態地獲得服務的例項,也無法針對不同的服務型別向元件提供一個管理服務例項的中心位置;
  • 由於元件與服務之間的這種關聯依賴,使得專案的開發過程受到約束。在實際專案中,開發過程往往是並行的,但又不是完全同步的,比如元件的開發跟其所需要的服務的開發同時進行,但很有可能當元件需要呼叫服務時,服務卻還沒完成開發和單體測試。遇到這種問題時,通常會將元件呼叫服務的部分暫時空缺,待到服務完成開發和單體測試之後,將其整合到元件的程式碼中。但這種做法不僅費時,而且增大了出錯的風險;
  • 針對元件的單體測試變得複雜。每當對元件進行單體測試時,不得不為其配置並執行所需要的服務,而無法使用Service Stub來解決元件與服務之間的依賴;
  • 在元件中可能存在多個地方需要引用服務的例項,在這種情況下,直接建立服務例項的程式碼會散佈到整個程式中,造成一段程式存在多個副本,大大增加維護和排錯成本;
  • 當元件需要呼叫多個服務時,不同服務初始化各自例項的方式又可能存在差異。開發人員不得不瞭解所有服務初始化的API,以便在程式中能夠正確地使用這些服務;
  • 某些服務的初始化過程需要耗費大量資源,因此多次重複地初始化服務會大大增加系統的資源佔用和效能損耗。程式中需要有一個管理服務初始化過程的機制,在統一初始化介面的同時,還需要為程式提供部分快取功能。

要解決以上問題,我們可以在應用程式中引入服務定位器(Service Locator)模式。

服務定位器(Service Locator)模式是一種企業級應用程式體系結構模式,它能夠為應用程式中服務的建立和初始化提供一箇中心位置,並解決了上文中所提到的各種設計和開發問題。

服務定位器模式和依賴注入模式都是控制反轉(IoC)模式的實現。我們在服務定位器中註冊給定介面的服務例項,然後通過介面獲取服務並在應用程式碼中使用而不需要關心其具體實現。我們可以在啟動時配置並注入服務提供者。

如果你瞭解 Laravel 框架,你對這一流程會很熟悉,沒錯,這就是 Laravel 框架的核心機制,我們在服務提供者中繫結介面及其實現,將服務例項註冊到服務容器中,然後在使用時可以通過依賴注入或者通過服務介面/別名獲取服務例項的方式呼叫服務。

  • 服務(Service) - 實際處理請求的服務。對這種服務的引用可以在 JNDI 伺服器中查詢到。
  • Context / 初始的 Context - JNDI Context 帶有對要查詢的服務的引用。
  • 服務定位器(Service Locator) - 服務定位器是通過 JNDI 查詢和快取服務來獲取服務的單點接觸。
  • 快取(Cache) - 快取儲存服務的引用,以便複用它們。
  • 客戶端(Client) - Client 是通過 ServiceLocator 呼叫服務的物件。

傳輸物件模式

傳輸物件模式(Transfer Object Pattern)用於從客戶端向伺服器一次性傳遞帶有多個屬性的資料。傳輸物件也被稱為數值物件。傳輸物件是一個具有 getter/setter 方法的簡單的 POJO 類,它是可序列化的,所以它可以通過網路傳輸。它沒有任何的行為。伺服器端的業務類通常從資料庫讀取資料,然後填充 POJO,並把它傳送到客戶端或按值傳遞它。對於客戶端,傳輸物件是隻讀的。客戶端可以建立自己的傳輸物件,並把它傳遞給伺服器,以便一次性更新資料庫中的數值。以下是這種設計模式的實體。

  • 業務物件(Business Object) - 為傳輸物件填充資料的業務服務。
  • 傳輸物件(Transfer Object) - 簡單的 POJO,只有設定 / 獲取屬性的方法。
  • 客戶端(Client) - 客戶端可以傳送請求或者傳送傳輸物件到業務物件。

委託模式

委託是對一個類的功能進行擴充套件和複用的方法。它的做法是:寫一個附加的類提供附加的功能,並使用原來的類的例項提供原有的功能。
假設我們有一個 TeamLead 類,將其既定任務委託給一個關聯輔助物件 JuniorDeveloper 來完成:本來 TeamLead 處理 writeCode 方法,Usage 呼叫 TeamLead 的該方法,但現在 TeamLead 將 writeCode 的實現委託給 JuniorDeveloper 的 writeBadCode 來實現,但 Usage 並沒有感知在執行 writeBadCode 方法。

示例程式碼

Usage.php

<?php

namespace DesignPatterns\More\Delegation;

// 初始化 TeamLead 並委託輔助者 JuniorDeveloper
$teamLead = new TeamLead(new JuniorDeveloper());

// TeamLead 將編寫程式碼的任務委託給 JuniorDeveloper
echo $teamLead->writeCode();

TeamLead.php

<?php

namespace DesignPatterns\More\Delegation;

/**
 * TeamLead類
 * @package DesignPatterns\Delegation
 * `TeamLead` 類將工作委託給 `JuniorDeveloper`
 */
class TeamLead
{
    /** @var JuniorDeveloper */
    protected $slave;

    /**
     * 在建構函式中注入初級開發者JuniorDeveloper
     * @param JuniorDeveloper $junior
     */
    public function __construct(JuniorDeveloper $junior)
    {
        $this->slave = $junior;
    }

    /**
     * TeamLead 喝咖啡, JuniorDeveloper 工作
     * @return mixed
     */
    public function writeCode()
    {
        return $this->slave->writeBadCode();
    }
}

JuniorDeveloper.php

<?php

namespace DesignPatterns\More\Delegation;

/**
 * JuniorDeveloper 類
 * @package DesignPatterns\Delegation
 */
class JuniorDeveloper
{
    public function writeBadCode()
    {
        return "Some junior developer generated code...";
    }
}

資源庫模式

Repository 是一個獨立的層,介於領域層與資料對映層(資料訪問層)之間。它的存在讓領域層感覺不到資料訪問層的存在,它提供一個類似集合的介面提供給領域層進行領域物件的訪問。Repository 是倉庫管理員,領域層需要什麼東西只需告訴倉庫管理員,由倉庫管理員把東西拿給它,並不需要知道東西實際放在哪。
Repository 模式是架構模式,在設計架構時,才有參考價值。應用 Repository 模式所帶來的好處,遠高於實現這個模式所增加的程式碼。只要專案分層,都應當使用這個模式。

示例程式碼

Post.php

<?php

namespace DesignPatterns\More\Repository;

/**
 * Post 類
 * @package DesignPatterns\Repository
 */
class Post
{
    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $text;

    /**
     * @var string
     */
    private $author;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param string $author
     */
    public function setAuthor($author)
    {
        $this->author = $author;
    }

    /**
     * @return string
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * @param \DateTime $created
     */
    public function setCreated($created)
    {
        $this->created = $created;
    }

    /**
     * @return \DateTime
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * @param string $text
     */
    public function setText($text)
    {
        $this->text = $text;
    }

    /**
     * @return string
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
}

PostRepository.php

<?php

namespace DesignPatterns\More\Repository;

use DesignPatterns\More\Repository\Storage;

/**
 * Post 對應的 Repository
 * 該類介於資料實體層(Post) 和訪問物件層(Storage)之間
 *
 * Repository 封裝了持久化物件到資料儲存器以及在展示層顯示物件導向的檢視操作
 *
 * Repository 還實現了領域層和資料對映層的分離和單向依賴
 *
 * PostRepository 類
 * @package DesignPatterns\Repository
 */
class PostRepository
{
    private $persistence;

    public function __construct(Storage $persistence)
    {
        $this->persistence = $persistence;
    }

    /**
     * 通過指定id返回Post物件
     *
     * @param int $id
     * @return Post|null
     */
    public function getById($id)
    {
        $arrayData = $this->persistence->retrieve($id);
        if (is_null($arrayData)) {
            return null;
        }

        $post = new Post();
        $post->setId($arrayData['id']);
        $post->setAuthor($arrayData['author']);
        $post->setCreated($arrayData['created']);
        $post->setText($arrayData['text']);
        $post->setTitle($arrayData['title']);

        return $post;
    }

    /**
     * 儲存指定物件並返回
     *
     * @param Post $post
     * @return Post
     */
    public function save(Post $post)
    {
        $id = $this->persistence->persist(array(
            'author' => $post->getAuthor(),
            'created' => $post->getCreated(),
            'text' => $post->getText(),
            'title' => $post->getTitle()
        ));

        $post->setId($id);
        return $post;
    }

    /**
     * 刪除指定的 Post 物件
     *
     * @param Post $post
     * @return bool
     */
    public function delete(Post $post)
    {
        return $this->persistence->delete($post->getId());
    }
}

Storage.php

<?php

namespace DesignPatterns\More\Repository;

/**
 * Storage介面
 *
 * 該介面定義了訪問資料儲存器的方法
 * 具體的實現可以是多樣化的,比如記憶體、關係型資料庫、NoSQL資料庫等等
 *
 * @package DesignPatterns\Repository
 */
interface Storage
{
    /**
     * 持久化資料方法
     * 返回新建立的物件ID
     *
     * @param array() $data
     * @return int
     */
    public function persist($data);

    /**
     * 通過指定id返回資料
     * 如果為空返回null
     *
     * @param int $id
     * @return array|null
     */
    public function retrieve($id);

    /**
     * 通過指定id刪除資料
     * 如果資料不存在返回false,否則如果刪除成功返回true
     *
     * @param int $id
     * @return bool
     */
    public function delete($id);
}

MemoryStorage.php

<?php

namespace DesignPatterns\More\Repository;

use DesignPatterns\More\Repository\Storage;

/**
 * MemoryStorage類
 * @package DesignPatterns\Repository
 */
class MemoryStorage implements Storage
{

    private $data;
    private $lastId;

    public function __construct()
    {
        $this->data = array();
        $this->lastId = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function persist($data)
    {
        $this->data[++$this->lastId] = $data;
        return $this->lastId;
    }

    /**
     * {@inheritdoc}
     */
    public function retrieve($id)
    {
        return isset($this->data[$id]) ? $this->data[$id] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($id)
    {
        if (!isset($this->data[$id])) {
            return false;
        }

        $this->data[$id] = null;
        unset($this->data[$id]);

        return true;
    }
}

參考連結

No practice, no gain in one's wit.
我的 Gitub

相關文章