一、引入
設計模式的一般定義不再說,只大概說一下我理解的設計模式,我理解的設計模式的主要目的是利用面向對象(類、接口等)特點,讓代碼更加易於擴展,易於重用,易於維護。這三個特點也就要求我們不要將太多功能積攢到一個類裡面,而是分配到更多的類裡面。所以,二十種乃至更多的設計模式主要是圍繞上述四個目的進行設計的。
php設計模式這一本書講了19種設計模式,但其實有大部分設計模式思想上或者設計上是一樣的思維與形式,我將在下面進行歸類和總結,以便於大家更好地理解這本書,但大家最好看一下這本書,裡面的使用的例子比較簡單,看書的時候記住這本書為了代碼上更簡單,有些應該通過實現接口來更加規范的地方沒有加接口。
(注意:由於本文是對《php設計模式》這本書進行總結,所以時間原因書中的例子我有所提到但是並沒有寫在本文中,大家可以通過查看電子書,以後我也會在分別介紹各個模式的文章或者本文中補充例子)
二、創建類的時候需要考慮的模式
面向對象最終要的概念就是類,從面向過程轉換到對象,類是重要的步驟。那麼創建一個類,各個類之間的依賴如何來實現,就是一個很重要的問題,下面的幾個模式就是針對這一問題的。
1、建造者模式
我們應該在代碼中盡量減少new的出現,這是創建類的一個重要方式沒錯,但是我們應該讓他盡量出現在建造者或者說工廠中,而不應該出現在一個類的方法中。原因一,可能這個類的初始化方式比較麻煩,不光需要new,還需要往其new的構造函數裡面傳參,那麼我們首先要建立那些參數,而那些參數中可能還有對象需要new,這個初始化操作就特別長;原因二,很可能這個類以後需求可能會改變,那麼初始化操作我們就需要改變,改變的操作也是在別的類裡進行的,這不符合各個類之間獨立的原則。
2、工廠模式--延伸的建造者模式
當很多代碼和框架中說道工廠模式的時候,其實它做的事情就是建造者模式,我認為工廠模式的意義更多的是讓建造者模式更有彈性的抽象工廠。接著建造者模式的概念,如果有一些類有著一套標准得創建過程,那麼我們可以先建一個抽象工程,然後各個具體類繼承這個類,pizza工廠就是一個很好的例子。
3、補充:服務的概念--眾多php框架的核心,依賴注入的實現方式。依賴注入比較重要,我將在別的文章裡進行說明。
如果我們看php的兩大框架symfony和zend framework我們會發現有個很重要的概念叫做依賴注入(dependency injection),也就是如果解決類之間依賴的問題,實現的方式有個很重要的概念叫做 service,或者說service container。在我的理解上這和工廠模式很類似,zf中也可以將一個服務指向一個具體的工廠,而在工廠的類中我們也可以實現抽象工廠,所以說這兩個是概念上類似,但是並不沖突的方式。之後會單獨介紹。
三、當需求改變或增加功能時
以下模式能夠方便我們重用代碼
1、適配器模式
我們說當功能變化的時候,我們應該保證類的接口不變,這樣可以保證一個類變化的時候,它使用或者使用它的類的代碼不需要變化。但是,畢竟會有違背這個原則的代碼和類出現,比如書中所舉例子,本來分解errorobject的功能應該由發生需求改變的類logtocsvadapter來實現,但是可能出於很多原因(比如代碼是不可更改,不開源的),沒有這麼做。而適配器模式就是用來處理這種情況,他可以通過extend(比如書中的用法),也可以通過將其作為成員變量的方式,重新實現一個符合要求的接口。
2、裝飾器模式
形式:將被裝飾的類作為其成員,通過成員調用方法。
當你想為一個類添加功能卻又害怕改壞了一個類的時候可以使用這種模式,但這種想法終究不能從根本上解決問題。
另一種常用的場景是如果你只想展示給別人這個類的某一些功能而不是全部的功能。
3、中介者模式(類似事件模式)
形式:多個有聯系的類將中介者類作為其成員,調用某個方法會通過中介者來改變所有在中介者中注冊的類的狀態,所以注冊的類需要實現一個接口供中介者調用。
和觀察者類似。
結構上類似事件模式,只不過注冊是寫死(hardcode)在中介者(對應事件中的注冊機)中,但是其解決問題的思路是事件的一個特例,所以叫做中介者模式。
假設有一個類,又有一個類在概念中和第一個類是平行的(可能是一開始就有的類,也可能是隨著需求的變化又出現的一個類),這兩個類有聯系,一個類的改變需要改變另一個類的狀態,這個時候需要中介者。具體例子會在事件模式中介紹。
四、將功能分散到更多的類中
實際項目中,維護一個有很多功能的“大”類是很麻煩的,這不符合高內聚低耦合的原則,一個類如果功能越多,那麼就越面向過程,各個功能的聯系越緊密,其實面向對象要解決的主要問題也是如何用更易於管理的方式將功能分布到更多的類中,聽起來簡單,實際上如果使用不好的方式分出來的類反而會更亂,從某種意義上這也是設計模式出現的原因。還有比如說一個人負責維護一個類,負責這個類的測試,如果結果每當一個功能發生變化他都要更改這個類的測試代碼,如果我們將功能分布到更多的類中,就可以保證該類的測試代碼不發生變化。
1、事件模式(會單獨寫一篇文章詳細介紹,這裡簡介)
很多人包括我對事件的印象留在窗口進程中,我們單擊鼠標或者按鈕會有一個事件,它會改變所有注冊了該事件的對象,可能是視圖發生變化,也可能是我們自己定義的一個函數,中介者模式也類似事件模式,只不過是所有在中介者中注冊的都是平等的關系,比如一個表格和一個柱狀圖,我們改變表格的內容會引起柱狀圖的改變,我們縮短加長柱狀圖會改變表格的內容。但是在一般的應用邏輯中事件模式和中介者模式有什麼用?
我們要拋開上面所說的事件的意義,上面只是事件的一個特例,這裡說明一下事件更一般的用法。其實事件本質上是中介者模式,中介者是把注冊的容器放在了需要被改變的類中,相當於事件中傳入event中的對象,但有些類在一開始並沒有設計成中介者模式,也就是說類中沒有保存一個中介者的成員,可能是因為沒想到這個需求,也可能是因為覺得在類中加入這個機制比較麻煩,而是選擇了可以在類外實現相同功能的事件模式。(所以說中介者模式是可以轉換成事件模式的,雖然事件模式免去了在類內的麻煩,但是卻需要提供一套事件的機制,相對於中介者在類外更加麻煩的,但由於分離到類外,所以有很多現成的中介者實現,比如各個框架基本都實現了這一機制,symfony更是可以將這一組件用在不是symfony的框架中。
在除了上面視圖界面的操作中,別的例子可能不是那麼直觀,不那麼符合事件這個名字,它的主要作用就像大標題中所寫的那樣,是通過事件模式的代碼結構來更好的分割代碼的功能使其更易管理。所以要理解就要忘記事件的一般含義。思考一個邏輯比較復雜的例子,一個電話公司,會針對用戶的通話記錄計算其花費,眾所周知這是一套很復雜的邏輯,根據用戶的不同套餐和使用地區都會有差別,那麼那麼一長串邏輯判斷代碼我們難道要寫在一個類中麼?肯定不符合我們的原則,那麼我們就利用事件模式的形式(反復強調只是形式),來實現將邏輯分到不同的類中。我們把所有的電話服務都作為listener,進行注冊,每一條通話記錄作為subscriber,都通過if語句來判斷屬於哪一個業務,然後對向那些業務dispatch,listener獲取賬單對象,對其進行更改,最後的賬單對象就是最終的賬單信息。通過事件模型,我們將不同的處理邏輯分出來成為listener對象,更方便以後的擴展。
上面的例子是通過listener修改對象,也可以僅僅是利用獲得對象信息進行相關操作,比如博客園發布一篇博文會有很多的操作,比如需要存數據庫,需要進行倒排操作,需要進行標簽操作,如果要發布到首頁也需要另外的額一系列操作,可以通過一些listener來監聽發布博文這一個操作,本身並沒有修改Post這個對象,而是利用Post對象信息進行操作。
回到更一般的說法,事件模式主要應用在插件(plugin),當然是更廣義的插件。這是symfony的介紹Consider the real-world example where you want to provide a plugin system for your project. A plugin should be able to add methods, or do something before or after a method is executed, without interfering with other plugins. This is not an easy problem to solve with single and multiple inheritance (were it possible with PHP) has its own drawbacks.
從上面的意義上講,事件是以一種更利於擴展的方式處理和加工對象的機制,在你想利用這個機制的時候都可以用事件,並且借助現有框架中的事件組件,你可以將觀察者和中介者都轉化為事件模式,所以事件其實很常用,在c#中事件的重視中也可以體現。
也可以看一下symfony事件文檔來學習一下事件的運用。
2、觀察者模式
形式:$this->obserber->update($this),而中介者的形式是$this->mediator->chage($this,array('band'=>$newname))。可以看出來只不過是為了完成兩種不同的功能需求。而這兩者都可以通過事件來代替,前者發送一個update事件,後者發送一個changeBandName事件,然後通過查看傳入對象的name來實現change。
3、委托模式
通過分配或者委托至其他對象,委托設計模式能夠去除核心對象中判決(if語句)和復雜的功能性。和c#中委托的概念相似,c#中是用函數的形式,將委托作為參數傳到別的函數中,從而將功能性分到別的方程。面向對象裡的委托是把功能性分到委托類中。
形式:被委托類中有一個委托類的成員變量。
4、策略模式
在能夠創建應用於基於對象的、由自包含算法組成的可互換對象時,最佳做法是使用策略模式。
和委托模式基本一致,無論是形式還是意義上,只不過書中的例子委托模式是把$this->playlist傳遞給了委托模式,但是策略模式把整個$this都傳給了策略模式。
策略模式還有一個重要的意義,如果一個類的某個方法不經常用,可以轉換成策略模式,這樣就不必在類的測試中每次都要測試這個類了。
5、代理模式
代理模式可以有不同的形式,只要是被賦予了“代理”這個意義。書中通過裝飾器的繼承形式實現了代理。
五、其他
以下都是很常見的模式
1、面向對象與生俱來的模式--模板模式
就不詳細介紹了,和抽象類和接口是一個思想。
2、外觀模式(facade)
facade這個單詞在一些框架中類的命名中很常見,類似面向過程中function的作用,把一系列針對某個特定功能的代碼封裝到一個類中一個static方法中,通常該類也只有這一個方法。
3、公共方法抽象成類--數據訪問對象模式
公共方法抽象成類是面向對象的基本原則,不僅是一個實體,如果是一個功能可以被多個實體或者功能所使用,那麼我們應該為他單獨成類,其他類如果使用它那也就是上文所說的依賴注入(dependency injection)。
數據訪問對象模式也是這一模式的基本實踐。可以結合zend famework的guide http://framework.zend.com/manual/current/en/in-depth-guide/preparing-db-backend.html 進行理解。基本上是為了封裝數據的操作,tablegateway也是這一領域的成熟的模式,基本上是一個表一個類,但這樣也就限制了數據庫本身的連接操作,我們只能通過類之間的操作來實現連接,具體我會在其他文章中寫。
4、系統中只需要一個-單元素模式
這個模式也很常見
六、書中關鍵句子筆記
1、不同對象的連接才是簡化的目標