1. 程式人生 > >PHP的設計模式及場景應用介紹

PHP的設計模式及場景應用介紹

而不是 param func 內部 api 必須 應該 coin 求一個

有大量的文章解釋什麽是設計模式,如何實現設計模式,網絡上不需要再寫一篇這樣的文章。相反,在本文中我們更多的討論什麽時候用和為什麽要用,而不是用哪一個和如何使用。

我將會為這些設計模式描繪不同的場景和案例,和提供一個簡短的定義幫助你們中對這些指定的模式不熟悉的人。讓我們開始吧。

這篇文章覆蓋了Robert C. Martin書中提到的一些敏捷設計模式。這些設計模式都是最初由四人組在1994年定義和發表的設計模式的現代改寫版。Martin的模式更多是對四人組模式的承接,能更好的與現在的編程技術協作。

讓我們從創建對象開始

使用工廠模式

工廠模式用來幫助程序員管理要創建的對象的相關信息。有時對象構造方法的參數有很多,有時這些參數必須用默認的信息填充。這些對象應該在工廠內創建,把它們的生成和初始化信息放在同一個地方。

  • When:當你發現你需要搜集很多信息來創建對象時使用工廠模式
  • Why:工廠幫助我們把對象創建的邏輯放在同一個地方。同樣也可以打破依賴,促進松散耦合和依賴註入,讓測試更方便。

尋找我們需要的數據

這裏有兩個常用的模式用於從持久層或外部數據源檢索信息。

網關模式

這個模式在持久化解決方案和業務邏輯間定義了一個通信頻道。對於簡單的應用程序,它可以通過自己檢索或重建所有對象,但在復雜的應用程序中對象的創建是工廠的責任。網關只是簡單的檢索和保存行數據。

  • When:當你需要檢索或保存信息時
  • Why:它為復雜的持久化操作提供了簡單的公共接口。它同樣封裝了持久化的信息,解除了業務邏輯和持久化邏輯間的耦合

事實上,網關模式是另一種設計模式的一個特別的實現,我們將在稍後討論:適配器模式。

代理模式

有時你不能(不想)把持久層的信息暴露給你的業務類。代理模式(proxy pattern)是一個很好的方式:欺騙你的業務類,讓它們認為是在使用已經存在的對象。

  • When:當你不得不從持久層或外部源檢索信息,但是又不想讓業務邏輯知道這些
  • Why:提供了一個非入侵式的方式在幕後創建對象。它同樣使得動態的、方便的從不同源檢索數據成為可能。

一個代理(proxy)有效實現了一個接口像真正的對象那樣,有同樣的功能。業務邏輯簡單的把它當作真正的對象使用,但實際上,如果對象不存在代理將創建一個。

好吧,好吧。那樣是很棒,但是我們如何找到需要創建的對象呢?

請求存儲

倉儲模式對實現search方法和迷你查詢語言是很有用的。它執行查詢,使用一個網關封裝數據,然後提供給工廠方法創建你需要的對象。

倉儲模式同其它的設計模式有點不一樣,它是屬於領域驅動設計的一部分,也不是Robert C. Martin的書中包含的。

  • When:你需要根據檢索條件創建多個對象,或者你需要保存多個對象到持久層。
  • Why:為了讓客戶端能有特定的對象來處理通用的和單獨的查詢和持久化的語言。它從業務邏輯中移除了跟創建對象相關的代碼。

如果倉庫找不到對象呢?一個選擇是返回NULL值,但是這樣會有兩個副作用:

  • 當你嘗試調研這樣一個對象的一個方法時,會拋出一個Refused Bequest(被拒絕的遺贈)。
  • 它會強迫你在代碼中做很多null的檢測(if(is_null($param)) return;)。

一個比較好的方法是返回一個空對象。

空對象模式

一個空對象實現了其它對象同樣的接口,但是空對象的成員方法返回原始值。比如:一個返回字符串的方法將返回空字符串;另一個返回數字的方法將返回0。這要求你讓方法不要返回有意義的值,但是你使用這些對象就不用擔心請求被拒絕和去除null的檢測代碼。

  • When:你頻繁的檢測null或遇到Refused Bequest(被拒絕的遺贈)
  • Why:它可以增加你代碼的透明度和迫使你更多的考慮對象的行為。

It’s not unusual to call many methods on an object before it can do its job. There are situations when you must prepare an object after its creation before you can truly use it. This leads to code duplication when creating those objects in different places.

你需要命令模式

  • When:當你要為初始化對象執行很多操作時。
  • Why:去除客戶端代碼創建對象時的復雜性。

聽起來不錯,不是嗎?事實上,它在很多情況下是很有用的。命令模式被廣泛的用來執行事務。如果你給控制對象添加一個簡單的undo()方法, 它可以跟蹤所有的要取消的它執行過的事務,如果有必要還可以顛倒順序。

現在你又有10個(或更多)的命令對象,你希望它們能同時工作。你可以把它們都聚集到一個主動對象。

主動對象

簡單而有趣的主動對象只有一個職責:存儲一系列的命令對象,和調用它們。

  • When:用同一個命令執行幾個相似的對象
  • Why: 它使得客戶端代碼執行一個任務就能影響多個對象

一個主動對象在執行完一個命令後會把它從列表中移除,也就是說,一個命令你只能執行一次。幾個主動對象的真實案例:

  • 購物車:執行每個產品的buy()命令,然後再從購物車中刪除掉。
  • 金融交易:把交易事項壓入一個隊列,用一個簡單的活動對象作為隊列管理者,執行交易並從隊列中刪除。

主動對象模式在早期的多任務系統中也發揮了作用。active對象內的每個對象都要保存一個該active對象的引用。這樣它們可以執行完自己的工作後,把自己壓入隊列。即時在今天的系統裏面,你也可以調用一個對象工作,然後等待另一個系統的響應。

可重用性

我相信你聽說過面向對象編程的重大承諾:代碼重用。早期的OOP采納者設想在數百萬不同的項目中使用通用的庫和類,但是這從未發生過。

用一些方法模版來代替

這個模式可以重用部分代碼,它適用於相互差別很小的運算。

  • When:用簡單的方式消除重復
  • Why:這樣就沒有重復和靈活性的問題了.

靈活性很棒,但是我真的需要嗎?

該使用策略模式了

  • When:靈活性和重用性比起代碼的簡明更重要
  • Why:用它來實現大型的、可交換的復雜邏輯塊,同時保持相同的邏輯特征

比如你創建了一個通用的Calculator,然後使用不同的ComputationStrategy來執行這個計算。這是一個被適度使用的模式,當你不得不定義很多條件行為時,它非常有用。

Discover-ability

隨著項目的發展,外部用戶訪問我們的應用變得越來越困難。這是一個理由:需要為討論中的應用和模塊提供一個明確定義的入口點。另外一些理由可能有想要隱藏模塊的內部運作和結構。

展示一個外觀(Present a Facade)

外觀(Facade)本質上是一個api——一個面向客戶端的友好接口。當客戶端調用其中的一個方法時,facade就委托它隱藏的其它類來提供客戶端需要的信息或結果。

  • When: 為了簡化API或刻意隱藏內部邏輯.
  • Why:你可以把API與具體的邏輯實現獨立開來

控制是很棒的,有時事情發生了變化,你需要執行一些任務。用戶必須被通知,紅燈還是閃爍,警報開始拉響…你可以用這個方法。

流行的Laravel框架把外觀模式用得很好

訂閱一名觀察者

觀察者模式提供了一個簡單的方法來監視對象,在條件發生變化時采取行動。這裏有兩種類型的觀察者實現:

  • 輪詢:對象接收觀察者。訂閱者觀察這個對象,特定事件發生時被通知。訂閱者詢問被觀察者更多的信息以便采取行動。
  • 推送:像輪詢一樣,對象接收觀察者,當特定事件發生時觀察者被通知。當一個通知發生時,觀察者收到一個暗示:可以行動了。

背景:

  • When:在你的業務邏輯中提供一個通知系統。
  • Why:這個模式提供了一種方式,可以在多個對象間進行事件通信。

這個模式的案例:郵件通知、後臺程序日誌或信息系統。當然在現實中,有無數的途徑來使用它。

正交性

中介者模式是觀察者模式的一個擴展。這個模式把兩個對象當做參數,中介者把它自己訂閱到第一個參數(把自己當做觀察者),當被觀察對象變化發生時,中介者決定第二個做什麽?

  • When:被影響的對象無法知道被觀察的對象時
  • Why:提供了一個在系統中一個對象變化時可以影響其他的對象的隱藏機制

單例

有時,你需要一些特別的對象:在系統中只有一個,你希望確保所有客戶端都能看見這些對象的變化。因為總總原因,你想要防止創建多個實例,比如:對一些第三方庫的初始化和並發操作的問題。

使用單例模式

一個單例對象擁有一個私有的構造函數,和一個公有的getInstance()方法。這樣就確保了這個對象只有一個實例存在。

  • when:你需要實現一個單一性和跨平臺的惰性計算的解決方案
  • Why:在需要的時候提供了一個單點訪問入口

Monostate模式

另一個實現單一性的途徑是Monostate模式。這種方式使用了面向對象編程語言提供的一個技巧,他有一個動態的共有方法獲取或者設置靜態私有變量的值。這樣就確保了所有的實例共享相同的值。

  • When:透明、多肽與單一性很好的結合
  • Why:向客戶端代碼隱藏了提供單一性的事實

要特別註意單一性。它可能會破壞全局命名空間,大多數情況下可以被其它更符合具體情況的方案代替。

控制不同的對象

假設你有一個開關和一個燈,開關可以控制開燈和關燈。但是,現在你購買了一個電風扇,想用舊的開關去控制它。使用開關、連接線,這在物理世界中是很容易實現的。

不幸的是,在編程世界裏卻並不容易。你有一個Switch 類和一個Light 類.如果你的開關在操作電燈,如何讓他操作風扇。

很簡單!復制、粘貼Switch類,修改它來操作風扇。但是這樣代碼重復了,這樣相當於是為風扇買了一個新的開關。你可以用FanSwitch繼承Switch,用一個對象代替。但是如果你需要一個按鈕或遠程控制呢?

The Abstract Server Pattern

這是有史以來最簡單的一個模式,它僅僅是提供一個接口。但是這裏有不同的實現。

  • When:你需要把幾個對象聯系起來,並且保持靈活性。
  • Why:因為它是實現靈活性最簡單的方式,它同時遵守了依賴反轉和開閉原則。

PHP是動態類型的。這意味著你可以省略接口,在相同的上下文中使用不同的對象——冒著拒絕的饋贈(refused bequest)的風險。同樣,php允許你定義接口,我建議你使用這個功能,讓你的源碼提供更清晰的意圖。

但是,你想說你已經有了很多類。沒錯。這裏有很多庫、第三方的api,還有其他的模塊,但是這並不意味著我們的業務邏輯知道這些細節。

插上適配器

適配器模式在業務邏輯和其它事物間建立了一個通信。我們已經見過這類模式了:網關模式。

  • When:你需要與已存在的、有潛在變化的模塊、庫、API建立連接
  • Why:讓你的業務邏輯只是依賴適配器提供的公共方法。

如果上面的模式都不適用與你的情況,你可以使用…

橋接模式

這是一個非常復雜的模式。我個人不喜歡它,因為通常可以選擇其它簡單的方法。但是在某些特殊情況下,其它的模式都失敗時,你可以考慮橋接模式。

  • When:當適配器模式不夠用,你要修改兩邊(業務邏輯和外部)的類。
  • Why:在特別復雜的成本下提供了更強大的靈活性。

組合模式

考慮下有個腳本裏面有很多相似的命令,你想通過一次調用來運行它們。等等,我們不是之前已經見過類似的情況?主動對象模式?

是的,我們見過了。但是,這次有點不一樣。這次是組合模式,它保持一個對象列表。調用組合對象的一個方法,會調用它的所有對象的相同的方法,並不把它們從列表中刪除。調用方法的客戶端以為他們是在請求一個特定類型的對象,事實上他們的行為被應用到很多相同的類型的對象上。

  • When:你必須應用一個動作到幾個類似的對象上
  • Why:減少重復和簡化類似的對象

這裏有個示例:你有一個應用可以創建和存儲訂單。假設你有3個訂單:$order1, $order2 和 $order3。你可以依次調用他們的place()方法,或者你可以把這些訂單放到一個$compositeOrder對象裏面,然後調用它的place()方法。這樣一來,在包含所有訂單的地方調用place().

狀態模式

有限狀態機(FSM)是一個含有有限數字狀態的模型。最簡單的實現方式是用可靠的Switch…case模式。每個case語句表示一個當前狀態機的狀態,它知道如何激活下一個狀態。

我們都知道switch…case不是很令人滿意,因為它在我們的對象上輸出了太多我們不想要的東西。所以忘記switch…case語句吧,我們應該考慮狀態模式(state pattern)。狀態模式是幾個對象的組合:一個對象負責管理調度,一個表示抽象狀態的接口,然後做幾種不同的實現——每種實現針對一個狀態。每個狀態知道下一個狀態是什麽,這個狀態可以通知負責調度的對象設置到隊列裏的下一個狀態。

  • When:需要實現像FSM那樣的邏輯
  • Why:為了消除switch…case的問題,和更好的概括每種狀態的含義。

一個食物自動售貨機可能有一個main類來引用state類,state類看起來可能像:WaitingForCoin, InsertedCoin, SelectedProduct, WaitingForConfirmation, DeliveringProduct, ReturningChange.每個狀態完成它自己的工作,然後創建下一個對象狀態並發送到負責調度的對象。

裝飾模式

有時你在一個應用中部署了一個類或模塊,你不能修改它,因為會從跟不上影響到系統。但是,同時你需要添加用戶需要的新功能。

裝飾模式就是針對這種情況的。它很簡單:保留現有功能,另外加一個新的。這是通過繼承原始類,在運行時提供新功能。舊的用戶繼續使用舊的對象,新用戶同時使用舊的和新的功能。

  • When:你不能修改舊的類,但是你不得不實現新的功能。
  • Why:它提供非侵入性的方式增加新的功能。

一個簡單的例子是打印數據。你為你的用戶打印文本文件,但是你又想要有能夠打印html的能力。裝飾模式就是這樣一個解決訪問,讓你同時保持兩種功能。

或者,接收一個訪問者

如果說你擴展功能時遇到的問題不同,比如:你有一個復雜的像樹狀結構的對象,你想在一次性的在每個節點上面添加功能。但是一個訪問者是一個可行的解決方案。但是不足之處是,訪問者模式要求修改舊的類,如果舊的類不能接受訪問者的話。

  • When:裝飾模式不再適合,增加一些復雜度是可以接受的。
  • Why:To allow and organized approach to defining functionality for several objects but at the price of higher complexity.

總結

設計模式可以幫助我們解決問題。作為一個執行的建議,不要用模式命名你的類。相反,找到一個正確抽象出來的正確的名稱。

有些人會說如果在你的命名裏面不包含模式名稱,其他的開發人員會很難理解你的代碼。如果很難識別一個模式,那問題是出在這個模式的實現上。

使用設計模式可以解決你的問題,但是只是在它們適用的情況下,千萬不要濫用。對於一些小問題你會找到好的解決方案。反之,只有在你用了其它的一些方案後,你會發現你需要一個設計模式。

如果你是新接觸設計模式,我希望這篇文章能夠給你帶來一些認識:設計模式在應用程序中是很有幫助的。感謝閱讀!

PHP的設計模式及場景應用介紹