工廠模式與OO設計原則

銀河使者發表於2008-05-06
如果把建立看作一個職責,那麼系統中的哪個物件應該擁有這個職責呢?如果把建立看作知識,那麼建立知識應該放置在什麼地方呢?說到職責我們不得不說一下著名的GRASP原則:

GRASP是通用職責分配軟體模式(General Responsibility Assignment Software patterns)的簡稱。它包含了9大模式,分別如下所示:

        1  建立者(Creator) :決定物件應該有誰來建立的問題。

        2  資訊專家(Information expert):用此模式來確定如何給物件分配職責的問題。一般把職責分配給那些包含此職責有關資訊的物件。這樣也體現了高內聚性模式。

        3 低耦合(Low coupling)

        4 控制器(Controller).

        5 高內聚(High Cohesion)

        6 多型性(polymorphism)

        7 純虛構(pure fabrication)

        8 間接性(indirection)

        9 防止變異(protected variations)
      可以看出GRASP非常關注物件由誰來建立,並給出了一個職責分配的解決模型:資訊專家模式;我們不必深究GRASP的種種細節,我們從上面的表述中可以得到這樣的啟示:

    * 物件的建立不是隨意性的,也是有規範可以遵循的,我們可以從中得到靈活性和可維護性;
    * 職責分配給這個職責相關的資訊專家,即:最適合的人做最適合的事情



       當建立一個物件的知識散佈在系統的各處的時候,這種蔓延現象實際上說明了系統建立職責分配的混亂,缺少一個進行擁有建立知識的資訊專家(請注意這裡的表述)。 建立邏輯通常是這樣的,它包含了一系列if-else的判斷邏輯,根據執行時的具體引數來決定物件的建立。如果這部分是確定的那麼我們也不必過多的考慮它。但是如果它是變化的,比如刪減一個物件的建立或者增加一個新型別的建立,那麼這部分程式碼是要被經常修改的,這種修改實際上說明我們已經違反了OCP原則(關閉修改,開啟擴充套件),那麼根據識別變化封裝變化的原則我們必須把這一部分進行抽取並進行隔離。
      翻看所有設計模式相關的書籍我們都可以看到這樣的表述:

      工廠模式專門負責將大量有共同介面的類例項化。工廠模式可以在執行時動態決定將哪一個類例項化。
   
     看樣子工廠模式正是我們需要的,但我們並不急於下手,看看是不是另有蹊徑,[介面]怎麼樣?《Java與模式》一書就是給出了這樣一個介面實現的方式。

       我習慣把介面理解成契約和行為,建立出來不同的物件,物件的區別實際上行為的差別。介面是不是可以?假設我麼使用了介面,問題解決了麼?沒有!增加新類時同樣面臨修改程式碼的問題。這個方法只是轉化了問題的形式,並沒有真正解決問題,使用介面我們失去的程式碼複用的優勢,而大量的具體類上使用介面不是什麼好主意,雖然《Java與模式》一書全部使用介面實現。如果要建立的物件沒有共同的業務邏輯那麼可以使用一個介面來扮演抽象產品的角色,但是更多的情況是具體產品之間是存在共有的業務邏輯,那麼這些邏輯就應該移到抽象角色裡面。
      其實我們就是將建立知識集中並設立一個資訊專家專門負責物件的建立。簡單工廠模式是我們的第一個擁有建立知識的資訊專家:
簡單工廠
      我們考慮最簡單的抽取和隔離方法就是使用[
簡單工廠
]。簡單工廠的特點就是引數化建立物件,簡單工廠必須知道每一種產品以及何時提供給Client。有人會說簡單工廠還是換湯不換藥,新增新類的時候還是需要修改這部分的程式碼!誠然,那麼我們獲得了什麼好處呢?集中變化! 這很好的符合了DRY原則(Don't Repeat Yourself!)建立邏輯存放在單一的位置,即使它變化,我們也只需要修改一處就可以了。DRY 很簡單,但卻是確保我們程式碼容易維護和複用的關鍵。DRY原則同時還提醒我們:對系統職能進行良好的分割!職責清晰的界限一定程度上保證了程式碼的單一性。這句話對我們後續的分析極具指導意義,畢竟簡單工廠只是低層次上的程式碼複用。
   

      題外話:簡單工廠的確簡單但是其背後的DRY原則在實踐中讓我們受益匪淺,去年我和我的搭檔做站點的升級工作,寫了很多重複的程式碼;程式碼重複,原始碼組織混亂,沒有做好規劃和職責分析是原罪。今年新專案,DRY原則是我頭頂的達摩克利斯之劍,不做重複的事情成為我進行專案計劃組織管理的重要標準。
    關於簡單工廠模式的階段總結:

    * 識別變化隔離變化,簡單工廠是一個顯而易見的實現方式
    * 簡單工廠將建立知識集中在單一位置符合了DRY
    * 客戶端無須瞭解物件的建立過程,某種程度上支援了OCP
    * 新增新的產品會造成建立程式碼的修改,這說明簡單工廠模式對OCP支援不夠
    * 簡單工廠類集中了所有的例項建立邏輯很容易違反高內聚的責任分配原則。
      

Factory Method模式

    簡單工廠模式之所以對OCP支援不夠,就是因為它擁有了太多的知識:有多少種產品以及建立產品的時機。或者說,簡單工廠承擔了太多的職責。DRY原則提示我們要清晰的劃清物件職責的界限。一個方法就是權力下放,將建立的知識移交給子類。知識的遞交意味著職責的轉移。
    這樣做之後,我們重新審視類之間的關係:核心工廠類不再負責所有產品的建立,核心類的層次上升成為一個抽象工廠角色,僅僅負責給出具體子類必須實現的介面,而不關係具體產品的建立細節。建立的知識由具體工廠擁有。而這時系統內也出現了一個平行的等級結構,產品家族的等級結構以及對應的工廠等級結構。
    簡單工廠把核心放在一個具體類上。Factory Method模式把核心放在抽象類,具體工廠類繼承了建立行為。具體工廠都擁有相同的介面所以還有一個別名叫多型工廠模式。這是我們關注點已經從實現轉移到了“介面”:關注動機而非實現,是基本的OO設計原則,將實現隱藏在介面之後實際上是將物件的實現與它們的物件解耦了。從“依賴”的角度,系統中依賴的已經不再是一個個具體的實現,而是一個抽象。這就是DIP原則:高層模組不應該依賴低層模組,兩者都應該依賴於抽象。這個原則隱含的意思是:物件之間只在概念層次存在耦合,在實現層次不能耦合!

     工廠方法模式的應用需要完全遵守里氏替換原則為前提。即父類出現的地方能夠替換成子類,工廠方法模式的應用才能成為可能。
      我們要新增一個新的產品,只要新增這個產品類和它的工廠類就可以了。沒有必要修改Client和已經存在的工廠程式碼。Factory Method完全支援OCP原則。

抽象工廠模式


    抽象工廠向客戶端提供了一個介面,使得客戶端在不指定具體產品型別的時候就可以建立產品族中的產品物件。這就是抽象工廠的用意。抽象工廠面的問題是多個等級產品等級結構的系統設計。抽象工廠和工廠方法模式最大的區別就在於後者只是針對一個產品等級結構;而抽象工廠則是面對多個等級結構。

      同樣出色的完成了把應用程式從特定的實現中解耦,工廠方法使用的方法是繼承,而抽象工廠使用的物件組合。抽象工廠提供的是一個產品家族的抽象型別,這個型別的子類完成了產品的建立。
       我們在工廠方法模式中提到的OCP DIP LSP等原則的表述也適用於抽象工廠模式.
       我曾經在《視角的力量--再說OO設計原則》一文中提到抽象出來高層策略是需要有一定穩定性的。抽象工廠作為一個高層抽象如果它的介面發生變化,那麼影響是巨大的:所有的子類都要進行修改!這就要求抽象工廠的介面設計是高度抽象的!


總結:

    * 發現變化隔離封裝變化是動因,關閉修改開啟擴充套件是限制
    * 簡單工廠很好地遵守了DRY原則,對OCP原則支援不足
    * 工廠方法模式完全支援了OCP原則,使用的機制是繼承
    * 抽象工廠模式 工廠方法模式都完全支援OCP原則
    * LSP原則是OCP成為可能的重要原則,抽象工廠模式、工廠方法模式完全遵守LSP
    * 依賴於抽象,一個類派生自具體類明顯違背了DIP原則
    * GRASP是另外一個設計模式體系,它與GOF設計模式在很多地方是殊途同歸,瞭解一下GRASP可以幫助我們思考

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12921506/viewspace-263084/,如需轉載,請註明出處,否則將追究法律責任。

相關文章