因爲掘金字數文章限制在
20000
之內,刪減了一部分,能夠前往github
查看全文 超全的設計模式簡介(45種),該文建議配合 design-patterns-for-humans 中文版 一塊兒看。php
設計模式(Design pattern)表明了最佳的實踐,一般被有經驗的面向對象的軟件開發人員所採用。設計模式是軟件開發人員在軟件開發過程當中面臨的通常問題的解決方案。這些解決方案是衆多軟件開發人員通過至關長的一段時間的試驗和錯誤總結出來的。
設計模式是一套被反覆使用的、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石同樣。項目中合理地運用設計模式能夠完美地解決不少問題,每種模式在現實中都有相應的原理來與之對應,每種模式都描述了一個在咱們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是設計模式能被普遍應用的緣由。html
共有 23 種設計模式。這些模式能夠分爲三大類:前端
下面用一個圖片來總體描述一下設計模式之間的關係:java
開閉原則的意思是:對擴展開放,對修改關閉。在程序須要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。簡言之,是爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,咱們須要使用接口和抽象類,後面的具體設計中咱們會提到這點。ios
里氏代換原則是面向對象設計的基本原則之一。 里氏代換原則中說,任何基類能夠出現的地方,子類必定能夠出現。LSP 是繼承複用的基石,只有當派生類能夠替換掉基類,且軟件單位的功能不受到影響時,基類才能真正被複用,而派生類也可以在基類的基礎上增長新的行爲。里氏代換原則是對開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範。laravel
這個原則是開閉原則的基礎,具體內容:針對接口編程,依賴於抽象而不依賴於具體。git
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。它還有另一個意思是:下降類之間的耦合度。因而可知,其實設計模式就是從大型軟件架構出發、便於升級和維護的軟件設計思想,它強調下降依賴,下降耦合。程序員
最少知道原則是指:一個實體應當儘可能少地與其餘實體之間發生相互做用,使得系統功能模塊相對獨立。github
合成複用原則是指:儘可能使用合成 / 聚合的方式,而不是使用繼承。web
工廠模式(Factory Pattern)最經常使用的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
在工廠模式中,咱們在建立對象時不會對客戶端暴露建立邏輯,而且是經過使用一個共同的接口來指向新建立的對象。
意圖: 定義一個建立對象的接口,讓其子類本身決定實例化哪個工廠類,工廠模式使其建立過程延遲到子類進行。
主要解決: 主要解決接口選擇的問題。
什麼時候使用: 咱們明確地計劃不一樣條件下建立不一樣實例時。
如何解決: 讓其子類實現工廠接口,返回的也是一個抽象的產品。
關鍵代碼: 建立過程在其子類執行。
應用實例:
優勢:
缺點: 每次增長一個產品時,都須要增長一個具體類和對象實現工廠,使得系統中類的個數成倍增長,在必定程度上增長了系統的複雜度,同時也增長了系統具體類的依賴。這並非什麼好事。
使用場景:
注意事項: 做爲一種建立類模式,在任何須要生成複雜對象的地方,均可以使用工廠方法模式。有一點須要注意的地方就是複雜對象適合使用工廠模式,而簡單對象,特別是只須要經過 new 就能夠完成建立的對象,無需使用工廠模式。若是使用工廠模式,就須要引入一個工廠類,會增長系統的複雜度。
抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠建立其餘工廠。該超級工廠又稱爲其餘工廠的工廠。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
在抽象工廠模式中,接口是負責建立一個相關對象的工廠,不須要顯式指定它們的類。每一個生成的工廠都能按照工廠模式提供對象。
意圖: 提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
主要解決: 主要解決接口選擇的問題。
什麼時候使用: 系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。
如何解決: 在一個產品族裏面,定義多個產品。
關鍵代碼: 在一個工廠裏聚合多個同類產品。
應用實例: 工做了,爲了參加一些聚會,確定有兩套或多套衣服吧,好比說有商務裝(成套,一系列具體產品)、時尚裝(成套,一系列具體產品),甚至對於一個家庭來講,可能有商務女裝、商務男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產品。假設一種狀況(現實中是不存在的,要否則,無法進入共產主義了,但有利於說明抽象工廠模式),在您的家中,某一個衣櫃(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產品),每次拿這種成套的衣服時也天然要從這個衣櫃中取出了。用 OO 的思想去理解,全部的衣櫃(具體工廠)都是衣櫃類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產品),褲子(某一具體產品),這些具體的上衣其實也都是上衣(抽象產品),具體的褲子也都是褲子(另外一個抽象產品)。
優勢: 當一個產品族中的多個對象被設計成一塊兒工做時,它能保證客戶端始終只使用同一個產品族中的對象。
缺點: 產品族擴展很是困難,要增長一個系列的某一產品,既要在抽象的 Creator 里加代碼,又要在具體的裏面加代碼。
使用場景:
注意事項: 產品族難擴展,產品等級易擴展。
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。
注意:
意圖: 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
主要解決: 一個全局使用的類頻繁地建立與銷燬。
什麼時候使用: 當您想控制實例數目,節省系統資源的時候。
如何解決: 判斷系統是否已經有這個單例,若是有則返回,若是沒有則建立。
關鍵代碼: 構造函數是私有的。
應用實例:
優勢:
缺點: 沒有接口,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。
使用場景:
**注意事項:**getInstance() 方法中須要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入形成 instance 被屢次實例化。
建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個複雜的對象。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
一個 Builder 類會一步一步構造最終的對象。該 Builder 類是獨立於其餘對象的。
意圖: 將一個複雜的構建與其表示相分離,使得一樣的構建過程能夠建立不一樣的表示。
主要解決: 主要解決在軟件系統中,有時候面臨着 "一個複雜對象" 的建立工做,其一般由各個部分的子對象用必定的算法構成;因爲需求的變化,這個複雜對象的各個部分常常面臨着劇烈的變化,可是將它們組合在一塊兒的算法卻相對穩定。
什麼時候使用: 一些基本部件不會變,而其組合常常變化的時候。
如何解決: 將變與不變分離開。
關鍵代碼: 建造者:建立和提供實例,導演:管理建造出來的實例的依賴關係。
應用實例:
優勢:
缺點:
使用場景:
注意事項: 與工廠模式的區別是:建造者模式更加關注與零件裝配的順序。
原型模式(Prototype Pattern)是用於建立重複的對象,同時又能保證性能。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
這種模式是實現了一個原型接口,該接口用於建立當前對象的克隆。當直接建立對象的代價比較大時,則採用這種模式。例如,一個對象須要在一個高代價的數據庫操做以後被建立。咱們能夠緩存該對象,在下一個請求時返回它的克隆,在須要的時候更新數據庫,以此來減小數據庫調用。
意圖: 用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象。
主要解決: 在運行期創建和刪除原型。
什麼時候使用:
如何解決: 利用已有的一個原型對象,快速地生成和原型對象同樣的實例。
關鍵代碼:
應用實例:
優勢:
缺點:
使用場景:
注意事項: 與經過對一個類進行實例化來構造新對象不一樣的是,原型模式是經過拷貝一個現有對象生成新對象的。淺拷貝實現 Cloneable,重寫,深拷貝是經過實現 Serializable 讀取二進制流。
對象池(也稱爲資源池)被用來管理對象緩存。對象池是一組已經初始化過且能夠直接使用的對象集合,用戶在使用對象時能夠從對象池中獲取對象,對其進行操做處理,並在不須要時歸還給對象池而非銷燬它。
若對象初始化、實例化的代價高,且須要常常實例化,但每次實例化的數量較少的狀況下,使用對象池能夠得到顯著的性能提高。常見的使用對象池模式的技術包括線程池、數據庫鏈接池、任務隊列池、圖片資源對象池等。
固然,若是要實例化的對象較小,不須要多少資源開銷,就沒有必要使用對象池模式了,這非但不會提高性能,反而浪費內存空間,甚至下降性能。
<?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; } } 複製代碼
<?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); } } 複製代碼
<?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,又或者咱們有多個日誌記錄器,分別用於記錄調試信息和錯誤信息,這些均可以使用多例模式實現。
<?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。
<?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(); } } 複製代碼
<?php namespace DesignPatterns\Creational\StaticFactory; /** * FormatterInterface接口 */ interface FormatterInterface { } 複製代碼
<?php namespace DesignPatterns\Creational\StaticFactory; /** * FormatNumber類 */ class FormatNumber implements FormatterInterface { } 複製代碼
適配器模式(Adapter Pattern)是做爲兩個不兼容的接口之間的橋樑。這種類型的設計模式屬於結構型模式,它結合了兩個獨立接口的功能。
這種模式涉及到一個單一的類,該類負責加入獨立的或不兼容的接口功能。舉個真實的例子,讀卡器是做爲內存卡和筆記本之間的適配器。您將內存卡插入讀卡器,再將讀卡器插入筆記本,這樣就能夠經過筆記原本讀取內存卡。
意圖: 將一個類的接口轉換成客戶但願的另一個接口。適配器模式使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做。
主要解決: 主要解決在軟件系統中,經常要將一些 "現存的對象" 放到新的環境中,而新環境要求的接口是現對象不能知足的。
什麼時候使用:
如何解決: 繼承或依賴(推薦)。
關鍵代碼: 適配器繼承或依賴已有的對象,實現想要的目標接口。
應用實例:
優勢:
缺點:
使用場景: 有動機地修改一個正常運行的系統的接口,這時應該考慮使用適配器模式。
注意事項: 適配器不是在詳細設計時添加的,而是解決正在服役的項目的問題。
橋接(Bridge)是用於把抽象化與實現化解耦,使得兩者能夠獨立變化。這種類型的設計模式屬於結構型模式,它經過提供抽象化和實現化之間的橋接結構,來實現兩者的解耦。
這種模式涉及到一個做爲橋接的接口,使得實體類的功能獨立於接口實現類。這兩種類型的類可被結構化改變而互不影響。
意圖: 將抽象部分與實現部分分離,使它們均可以獨立的變化。
主要解決: 在有多種可能會變化的狀況下,用繼承會形成類爆炸問題,擴展起來不靈活。
什麼時候使用: 實現系統可能有多個角度分類,每一種角度均可能變化。
如何解決: 把這種多角度分類分離出來,讓它們獨立變化,減小它們之間耦合。
關鍵代碼: 抽象類依賴實現類。
應用實例:
優勢:
缺點: 橋接模式的引入會增長系統的理解與設計難度,因爲聚合關聯關係創建在抽象層,要求開發者針對抽象進行設計與編程。
使用場景:
注意事項: 對於兩個獨立變化的維度,使用橋接模式再適合不過了。
過濾器模式(Filter Pattern)或標準模式(Criteria Pattern)是一種設計模式,這種模式容許開發人員使用不一樣的標準來過濾一組對象,經過邏輯運算以解耦的方式把它們鏈接起來。這種類型的設計模式屬於結構型模式,它結合多個標準來得到單一標準。
組合模式(Composite Pattern),又叫部分總體模式,是用於把一組類似的對象看成一個單一的對象。組合模式依據樹形結構來組合對象,用來表示部分以及總體層次。這種類型的設計模式屬於結構型模式,它建立了對象組的樹形結構。
這種模式建立了一個包含本身對象組的類。該類提供了修改相同對象組的方式。
意圖: 將對象組合成樹形結構以表示 "部分 - 總體" 的層次結構。組合模式使得用戶對單個對象和組合對象的使用具備一致性。
主要解決: 它在咱們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程序能夠向處理簡單元素同樣來處理複雜元素,從而使得客戶程序與複雜元素的內部結構解耦。
什麼時候使用:
如何解決: 樹枝和葉子實現統一接口,樹枝內部組合該接口。
關鍵代碼: 樹枝內部組合該接口,而且含有內部屬性 List,裏面放 Component。
應用實例:
優勢:
缺點: 在使用組合模式時,其葉子和樹枝的聲明都是實現類,而不是接口,違反了依賴倒置原則。
使用場景: 部分. 總體場景,如樹形菜單,文件. 文件夾的管理。
注意事項: 定義時爲具體類。
裝飾器模式(Decorator Pattern)容許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。
這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
意圖: 動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾器模式相比生成子類更爲靈活。
主要解決: 通常的,咱們爲了擴展一個類常用繼承方式實現,因爲繼承爲類引入靜態特徵,而且隨着擴展功能的增多,子類會很膨脹。
什麼時候使用: 在不想增長不少子類的狀況下擴展類。
如何解決: 將具體功能職責劃分,同時繼承裝飾者模式。
關鍵代碼:
應用實例:
優勢: 裝飾類和被裝飾類能夠獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式能夠動態擴展一個實現類的功能。
缺點: 多層裝飾比較複雜。
使用場景:
注意事項: 可代替繼承。
外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端能夠訪問系統的接口。這種類型的設計模式屬於結構型模式,它向現有的系統添加一個接口,來隱藏系統的複雜性。
這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委託調用。
意圖: 爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
主要解決: 下降訪問複雜系統的內部子系統時的複雜度,簡化客戶端與之的接口。
什麼時候使用:
如何解決: 客戶端不與系統耦合,外觀類與系統耦合。
關鍵代碼: 在客戶端和複雜系統之間再加一層,這一層將調用順序. 依賴關係等處理好。
應用實例:
優勢:
缺點: 不符合開閉原則,若是要改東西很麻煩,繼承重寫都不合適。
使用場景:
注意事項: 在層次化結構中,可使用外觀模式定義系統中每一層的入口。
享元模式(Flyweight Pattern)主要用於減小建立對象的數量,以減小內存佔用和提升性能。這種類型的設計模式屬於結構型模式,它提供了減小對象數量從而改善應用所需的對象結構的方式。
享元模式嘗試重用現有的同類對象,若是未找到匹配的對象,則建立新對象。咱們將經過建立 5 個對象來畫出 20 個分佈於不一樣位置的圓來演示這種模式。因爲只有 5 種可用的顏色,因此 color 屬性被用來檢查現有的 Circle 對象。
意圖: 運用共享技術有效地支持大量細粒度的對象。
主要解決: 在有大量對象時,有可能會形成內存溢出,咱們把其中共同的部分抽象出來,若是有相同的業務請求,直接返回在內存中已有的對象,避免從新建立。
什麼時候使用:
如何解決: 用惟一標識碼判斷,若是在內存中有,則返回這個惟一標識碼所標識的對象。
關鍵代碼: 用 HashMap 存儲這些對象。
應用實例:
優勢: 大大減小對象的建立,下降系統的內存,使效率提升。
缺點: 提升了系統的複雜度,須要分離出外部狀態和內部狀態,並且外部狀態具備固有化的性質,不該該隨着內部狀態的變化而變化,不然會形成系統的混亂。
使用場景:
注意事項:
在代理模式(Proxy Pattern)中,一個類表明另外一個類的功能。這種類型的設計模式屬於結構型模式。
在代理模式中,咱們建立具備現有對象的對象,以便向外界提供功能接口。
意圖: 爲其餘對象提供一種代理以控制對這個對象的訪問。
主要解決: 在直接訪問對象時帶來的問題,好比說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象因爲某些緣由(好比對象建立開銷很大,或者某些操做須要安全控制,或者須要進程外的訪問),直接訪問會給使用者或者系統結構帶來不少麻煩,咱們能夠在訪問此對象時加上一個對此對象的訪問層。
什麼時候使用: 想在訪問一個類時作一些控制。
如何解決: 增長中間層。
關鍵代碼: 實現與被代理類組合。
應用實例:
優勢:
缺點:
使用場景: 按職責來劃分,一般有如下使用場景:
注意事項:
在瞭解數據映射模式以前,先了解下數據映射,它是在持久化數據存儲層(一般是關係型數據庫)和駐於內存的數據表現層之間進行雙向數據傳輸的數據訪問層。
數據映射模式的目的是讓持久化數據存儲層、駐於內存的數據表現層、以及數據映射自己三者相互獨立、互不依賴。這個數據訪問層由一個或多個映射器(或者數據訪問對象)組成,用於實現數據傳輸。通用的數據訪問層能夠處理不一樣的實體類型,而專用的則處理一個或幾個。
數據映射模式的核心在於它的數據模型遵循單一職責原則(Single Responsibility Principle), 這也是和 Active Record 模式的不一樣之處。最典型的數據映射模式例子就是數據庫 ORM 模型 (Object Relational Mapper)。
準確來講該模式是個架構模式。
依賴注入(Dependency Injection)是控制反轉(Inversion of Control)的一種實現方式。
咱們先來看看什麼是控制反轉。
當調用者須要被調用者的協助時,在傳統的程序設計過程當中,一般由調用者來建立被調用者的實例,但在這裏,建立被調用者的工做再也不由調用者來完成,而是將被調用者的建立移到調用者的外部,從而反轉被調用者的建立,消除了調用者對被調用者建立的控制,所以稱爲控制反轉。
要實現控制反轉,一般的解決方案是將建立被調用者實例的工做交由 IoC 容器來完成,而後在調用者中注入被調用者(經過構造器/方法注入實現),這樣咱們就實現了調用者與被調用者的解耦,該過程被稱爲依賴注入。
依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助咱們開發出鬆散耦合(loose coupled)、可維護、可測試的代碼和程序。這條原則的作法是你們熟知的面向接口,或者說是面向抽象編程。
門面模式(Facade)又稱外觀模式,用於爲子系統中的一組接口提供一個一致的界面。門面模式定義了一個高層接口,這個接口使得子系統更加容易使用:引入門面角色以後,用戶只須要直接與門面角色交互,用戶與子系統之間的複雜關係由門面角色來實現,從而下降了系統的耦合度。
<?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(); } } 複製代碼
<?php namespace DesignPatterns\Structural\Facade; /** * OsInterface接口 */ interface OsInterface { /** * halt the OS */ public function halt(); } 複製代碼
<?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 中流接口模式有着普遍使用,好比查詢構建器,郵件等等。
<?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)也叫作註冊樹模式,註冊器模式。註冊模式爲應用中常用的對象建立一箇中央存儲器來存放這些對象 —— 一般經過一個只包含靜態方法的抽象類來實現(或者經過單例模式)。
<?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 進去。
應用實例:
優勢:
缺點:
使用場景:
注意事項: 在 JAVA WEB 中遇到不少應用。
命令模式(Command Pattern)是一種數據驅動的設計模式,它屬於行爲型模式。請求以命令的形式包裹在對象中,並傳給調用對象。調用對象尋找能夠處理該命令的合適的對象,並把該命令傳給相應的對象,該對象執行命令。
意圖: 將一個請求封裝成一個對象,從而使您能夠用不一樣的請求對客戶進行參數化。
主要解決: 在軟件系統中,行爲請求者與行爲實現者一般是一種緊耦合的關係,但某些場合,好比須要對行爲進行記錄、撤銷或重作、事務等處理時,這種沒法抵禦變化的緊耦合的設計就不太合適。
什麼時候使用: 在某些場合,好比要對行爲進行 "記錄、撤銷 / 重作、事務" 等處理,這種沒法抵禦變化的緊耦合是不合適的。在這種狀況下,如何將 "行爲請求者" 與 "行爲實現者" 解耦?將一組行爲抽象爲對象,能夠實現兩者之間的鬆耦合。
如何解決: 經過調用者調用接受者執行命令,順序:調用者→接受者→命令。
關鍵代碼: 定義三個角色:
應用實例: struts 1 中的 action 核心控制器 ActionServlet 只有一個,至關於 Invoker,而模型層的類會隨着不一樣的應用有不一樣的模型類,至關於具體的 Command。
優勢:
缺點: 使用命令模式可能會致使某些系統有過多的具體命令類。
使用場景: 認爲是命令的地方均可以使用命令模式,好比:
注意事項: 系統須要支持命令的撤銷 (Undo) 操做和恢復 (Redo) 操做,也能夠考慮使用命令模式,見命令模式的擴展。
解釋器模式(Interpreter Pattern)提供了評估語言的語法或表達式的方式,它屬於行爲型模式。這種模式實現了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。
意圖: 給定一個語言,定義它的文法表示,並定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子。
主要解決: 對於一些固定文法構建一個解釋句子的解釋器。
什麼時候使用: 若是一種特定類型的問題發生的頻率足夠高,那麼可能就值得將該問題的各個實例表述爲一個簡單語言中的句子。這樣就能夠構建一個解釋器,該解釋器經過解釋這些句子來解決該問題。
如何解決: 構建語法樹,定義終結符與非終結符。
關鍵代碼: 構建環境類,包含解釋器以外的一些全局信息,通常是 HashMap。
應用實例: 編譯器、運算表達式計算。
優勢:
缺點:
使用場景:
注意事項: 可利用場景比較少,JAVA 中若是碰到能夠用 expression4J 代替。
迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環境中很是經常使用的設計模式。這種模式用於順序訪問集合對象的元素,不須要知道集合對象的底層表示。
迭代器模式屬於行爲型模式。
意圖: 提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內部表示。
主要解決: 不一樣的方式來遍歷整個整合對象。
什麼時候使用: 遍歷一個聚合對象。
如何解決: 把在元素之間遊走的責任交給迭代器,而不是聚合對象。
關鍵代碼: 定義接口:hasNext, next。
應用實例: JAVA 中的 iterator。
優勢:
缺點: 因爲迭代器模式將存儲數據和遍歷數據的職責分離,增長新的聚合類須要對應增長新的迭代器類,類的個數成對增長,這在必定程度上增長了系統的複雜性。
使用場景:
注意事項: 迭代器模式就是分離了集合對象的遍歷行爲,抽象出一個迭代器類來負責,這樣既能夠作到不暴露集合的內部結構,又可以讓外部代碼透明地訪問集合內部的數據。
中介者模式(Mediator Pattern)是用來下降多個對象和類之間的通訊複雜性。這種模式提供了一箇中介類,該類一般處理不一樣類之間的通訊,並支持鬆耦合,使代碼易於維護。中介者模式屬於行爲型模式。
意圖: 用一箇中介對象來封裝一系列的對象交互,中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互。
主要解決: 對象與對象之間存在大量的關聯關係,這樣勢必會致使系統的結構變得很複雜,同時若一個對象發生改變,咱們也須要跟蹤與之相關聯的對象,同時作出相應的處理。
什麼時候使用: 多個類相互耦合,造成了網狀結構。
如何解決: 將上述網狀結構分離爲星型結構。
關鍵代碼: 對象 Colleague 之間的通訊封裝到一個類中單獨處理。
應用實例:
優勢:
缺點: 中介者會龐大,變得複雜難以維護。
使用場景:
注意事項: 不該當在職責混亂的時候使用。
備忘錄模式(Memento Pattern)保存一個對象的某個狀態,以便在適當的時候恢復對象。備忘錄模式屬於行爲型模式。
意圖: 在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。
主要解決: 所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態,這樣能夠在之後將對象恢復到原先保存的狀態。
什麼時候使用: 不少時候咱們老是須要記錄一個對象的內部狀態,這樣作的目的就是爲了容許用戶取消不肯定或者錯誤的操做,可以恢復到他原先的狀態,使得他有 "後悔藥" 可吃。
如何解決: 經過一個備忘錄類專門存儲對象狀態。
關鍵代碼: 客戶不與備忘錄類耦合,與備忘錄管理類耦合。
應用實例:
優勢:
缺點: 消耗資源。若是類的成員變量過多,勢必會佔用比較大的資源,並且每一次保存都會消耗必定的內存。
使用場景:
注意事項:
當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。好比,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲型模式。
意圖: 定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。
主要解決: 一個對象狀態改變給其餘對象通知的問題,並且要考慮到易用和低耦合,保證高度的協做。
什麼時候使用: 一個對象(目標對象)的狀態發生改變,全部的依賴對象(觀察者對象)都將獲得通知,進行廣播通知。
如何解決: 使用面向對象技術,能夠將這種依賴關係弱化。
關鍵代碼: 在抽象類裏有一個 ArrayList 存放觀察者們。
應用實例:
優勢:
缺點:
使用場景:
注意事項:
在狀態模式(State Pattern)中,類的行爲是基於它的狀態改變的。這種類型的設計模式屬於行爲型模式。
在狀態模式中,咱們建立表示各類狀態的對象和一個行爲隨着狀態對象改變而改變的 context 對象。
意圖: 容許對象在內部狀態發生改變時改變它的行爲,對象看起來好像修改了它的類。
主要解決: 對象的行爲依賴於它的狀態(屬性),而且能夠根據它的狀態改變而改變它的相關行爲。
什麼時候使用: 代碼中包含大量與對象狀態有關的條件語句。
如何解決: 將各類具體的狀態類抽象出來。
關鍵代碼: 一般命令模式的接口中只有一個方法。而狀態模式的接口中有一個或者多個方法。並且,狀態模式的實現類的方法,通常返回值,或者是改變實例變量的值。也就是說,狀態模式通常和對象的狀態有關。實現類的方法有不一樣的功能,覆蓋接口中的方法。狀態模式和命令模式同樣,也能夠用於消除 if...else 等條件選擇語句。
應用實例:
優勢:
缺點:
使用場景:
注意事項: 在行爲受狀態約束的時候使用狀態模式,並且狀態不超過 5 個。
在空對象模式(Null Object Pattern)中,一個空對象取代 NULL 對象實例的檢查。Null 對象不是檢查空值,而是反應一個不作任何動做的關係。這樣的 Null 對象也能夠在數據不可用的時候提供默認的行爲。
在空對象模式中,咱們建立一個指定各類要執行的操做的抽象類和擴展該類的實體類,還建立一個未對該類作任何實現的空對象類,該空對象類將無縫地使用在須要檢查空值的地方。
在策略模式(Strategy Pattern)中,一個類的行爲或其算法能夠在運行時更改。這種類型的設計模式屬於行爲型模式。
在策略模式中,咱們建立表示各類策略的對象和一個行爲隨着策略對象改變而改變的 context 對象。策略對象改變 context 對象的執行算法。
意圖: 定義一系列的算法, 把它們一個個封裝起來, 而且使它們可相互替換。
主要解決: 在有多種算法類似的狀況下,使用 if...else 所帶來的複雜和難以維護。
什麼時候使用: 一個系統有許多許多類,而區分它們的只是他們直接的行爲。
如何解決: 將這些算法封裝成一個一個的類,任意地替換。
關鍵代碼: 實現同一個接口。
應用實例:
優勢:
缺點:
使用場景:
注意事項: 若是一個系統的策略多於四個,就須要考慮使用混合模式,解決策略類膨脹的問題。
在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式 / 模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬於行爲型模式。
意圖: 定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。
主要解決: 一些方法通用,卻在每個子類都從新寫了這一方法。
什麼時候使用: 有一些通用的方法。
如何解決: 將這些通用算法抽象出來。
關鍵代碼: 在抽象類實現,其餘步驟在子類實現。
應用實例:
優勢:
缺點: 每個不一樣的實現都須要一個子類來實現,致使類的個數增長,使得系統更加龐大。
使用場景:
注意事項: 爲防止惡意操做,通常模板方法都加上 final 關鍵詞。
在訪問者模式(Visitor Pattern)中,咱們使用了一個訪問者類,它改變了元素類的執行算法。經過這種方式,元素的執行算法能夠隨着訪問者改變而改變。這種類型的設計模式屬於行爲型模式。根據模式,元素對象已接受訪問者對象,這樣訪問者對象就能夠處理元素對象上的操做。
意圖: 主要將數據結構與數據操做分離。
主要解決: 穩定的數據結構和易變的操做耦合問題。
什麼時候使用: 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做 "污染" 這些對象的類,使用訪問者模式將這些封裝到類中。
如何解決: 在被訪問的類裏面加一個對外提供接待訪問者的接口。
關鍵代碼: 在數據基礎類裏面有一個方法接受訪問者,將自身引用傳入訪問者。
應用實例: 您在朋友家作客,您是訪問者,朋友接受您的訪問,您經過朋友的描述,而後對朋友的描述作出一個判斷,這就是訪問者模式。
優勢:
缺點:
使用場景:
注意事項: 訪問者能夠對功能進行統一,能夠作報表、UI、攔截器與過濾器。
規格模式(Specification)能夠認爲是組合模式的一種擴展。有時項目中某些條件決定了業務邏輯,這些條件就能夠抽離出來以某種關係(與、或、非)進行組合,從而靈活地對業務邏輯進行定製。另外,在查詢、過濾等應用場合中,經過預約義多個條件,而後使用這些條件的組合來處理查詢或過濾,而不是使用邏輯判斷語句來處理,能夠簡化整個實現邏輯。
這裏的每一個條件就是一個規格,多個規格/條件經過串聯的方式以某種邏輯關係造成一個組合式的規格。
咱們去銀行櫃檯辦業務,通常狀況下會開幾個我的業務櫃檯的,你去其中任何一個櫃檯辦理都是能夠的。咱們的訪問者模式能夠很好付諸在這個場景中:對於銀行櫃檯來講,他們是不用變化的,就是說今天和明天提供我的業務的櫃檯是不須要有變化的。而咱們做爲訪問者,今天來銀行多是取消費流水,明天來銀行多是去辦理手機銀行業務,這些是咱們訪問者的操做,一直是在變化的。
訪問者模式就是表示一個做用於某對象結構中的各元素的操做。它使你能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。
MVC 模式表明 Model-View-Controller(模型 - 視圖 - 控制器) 模式。這種模式用於應用程序的分層開發。
業務表明模式(Business Delegate Pattern)用於對錶示層和業務層解耦。它基本上是用來減小通訊或對錶示層代碼中的業務層代碼的遠程查詢功能。在業務層中咱們有如下實體。
組合實體模式(Composite Entity Pattern)用在 EJB 持久化機制中。一個組合實體是一個 EJB 實體 bean,表明了對象的圖解。當更新一個組合實體時,內部依賴對象 beans 會自動更新,由於它們是由 EJB 實體 bean 管理的。如下是組合實體 bean 的參與者。
數據訪問對象模式(Data Access Object Pattern)或 DAO 模式用於把低級的數據訪問 API 或操做從高級的業務服務中分離出來。如下是數據訪問對象模式的參與者。
前端控制器模式(Front Controller Pattern)是用來提供一個集中的請求處理機制,全部的請求都將由一個單一的處理程序處理。該處理程序能夠作認證 / 受權 / 記錄日誌,或者跟蹤請求,而後把請求傳給相應的處理程序。如下是這種設計模式的實體。
攔截過濾器模式(Intercepting Filter Pattern)用於對應用程序的請求或響應作一些預處理 / 後處理。定義過濾器,並在把請求傳給實際目標應用程序以前應用在請求上。過濾器能夠作認證 / 受權 / 記錄日誌,或者跟蹤請求,而後把請求傳給相應的處理程序。如下是這種設計模式的實體。