目錄
背景
引用Martin Fowler的觀點
我的新視角
對象使用的視角
分離對象的創建
實際編程中的分離觀點
總結
背景
這篇文章是我在Net Objectives工作時寫的我在那裡的工作是指導人們編寫有效的面向對象程序本文將介紹一些實用但不同以往的觀點用來解決每天出現的設計問題本文不關注對象做什麼而是對象的使用和對象的實例化基於這些觀點可以大規模地簡化和改進代碼以滿足將來維護的需要
引用Martin Fowler的觀點
在《UML精粹》第三版中Martin Fowler提出了對象設計的三個層面概念層規約層(Specification)實現層
概念層概念層中的對象是承擔一定職責的實體通常用抽象類和接口(Interface)來描述這些實體之間以各種方式相互聯系來實現應用系統的目標
如果我是一個對象在概念層我關心的是我的職責是什麼
規約層規約層中的對象通過它的公共方法來實現它的職責每個公共的方法都是對象按照指定的方式提供的服務
如果我是一個對象在規約層我關心是別人如何使用我
實現層實現層是對象的代碼對象通過代碼來實現它的職責
如果我是一個對象在實現層我關心的是我如何完成我的職責
對於系統中一個實體只立足於一個概念層次來工作有很有益處的同樣的把自己的思維過程劃分成三部分也是有利的
首先這樣有助於減弱藕合如果對象之間的關系保持在抽象層面上後期實現的子類之間的藕合會更弱這些意見是設計模式的作者Gang of Four他們提出的他們認為設計者應該面向接口作設計
其次能使系統內聚性更好結構更清楚因為我們能圍繞對象的職責來編寫實現細節而不是其它的方式這樣一個對象被明確的定義職責范圍而並不是包含一些無關的方法和狀態(這對眼前的問題豪無幫助)
最後其它開發者能因此有一個清楚的認識過程因為如果對一個問題讓一個人同時從多個層面去理解它那麼他將很容易洩氣
我的新觀點
下面才是正題我將推薦一些觀點這是些類似的特性它能幫助我們實現靈活性和健壯性而這正是我們一直在尋找的面向對象方法我把我的這些觀點概括為對象創建VS對象使用
我們看看下面這段代碼 ![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481722.jpg)
這是一個SignalProcessor的例子它使用ByteFilter的一個實現(HiPassFilter)來完成自己的一部分工作基本上這是不錯的作法內聚性是比較好的每個類是一個整體通過和其它類協作來完成自己的任務並且對於ByteFiler可以提供多種實現而不需要改變SignalProcessor的設計這是一個可拔插的設計並且很容易擴展
概念上SignalProcess是負責處理字節數組的信號規約方面SignalProcessor表現為一個返回字節數組的process()方法
而SignalProcessor的實現的方面我們看到SignalProcessor調用ByteFilter的實例在我們經過這裡的時候我們只需考慮它的規約(filter()方法)而不需考慮它的實現這樣很好干淨清楚
但是問題在於SignalProcessor和ByteFilter之間的調用混合了兩種不同的概念(創建的概念和使用的概念)SignalProcessor掌控了HiPassFilter這個類的創建同時它也使用這個實例來工作
這看起來好象微不足道並且實際中也會經常出現但是讓我思考一下這兩個職責使用對象和制造對象把它們看做相對獨立的事情並審視它們之間建立的藕合
關於對象使用的觀點
一個客戶對象要使用服務對象要通過服務對象提供的公共方法如果服務對象被引用為Object這種類型那麼就只有一些通用方法可以調用所以如果客戶對象要使用服務對象就要滿足以下三個條件之一
l 知道這個服務對象的實際類型
l 知道這個服務對象實現的某一個接口
l 知道這個服務對象繼承樹上端的一個基類
為了盡可能的降低藕合我們傾向於滿足後兩個條件之一這樣將來根據需要可以改變實際被使用的客戶對象而不需要改變客戶對象的代碼
換句話說理想情況下客戶對象應該依賴一個抽象而不是具體現存的一個類這樣將來就可以自由的添加不同的服務類而不需要維護客戶類的代碼特別在服務類被大范圍使用的情況下這種設計原則顯示猶為重要
對象創建的觀點
顯而易見地如果我們要避免客戶對象知道Bytefilters的具體的實現和這個對象如何創建這就是意味著就要有另一個東西在另一個地方知道這些信息
因此我提出一種獨特的概念使用和創建同樣的客戶對象不涉及對象的創建對象的創建者也不會涉及對象的使用我們通常稱這種創建者為工廠類
這種設計就意味著下面這種設計模型
![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481763.jpg)
需要認真考慮的是創建和使用之間最自然的藕合做到這一點任何東西發生變化時都不會提心吊膽地去維護
如果把ByteFilter的抽象和兩個具體實現看作多態性服務(ByteFilter是一個服務類但它有兩個版本而通過多態性這種機制來實現不同版之間的變化)而SignalProcessor這個類只以使用的視角關注這個服務ByteFilterFactory則是負責不同服務類的創建
而ByteFilter這個抽象類型(本質上也只是一種抽象)有一些做為接口的公共方法這個抽象類型是SignalProcessor類和ByteFilter這個多態性服務之間的藕合而SignalProcessor和兩個具體類之間不存在任何的藕合但前提是我們是一個好的OO程序員不會給接口隨意的添加方法
ByteFilterFactory和ByteFilter多態性服務之間的藕合就是另一種情況了這個工廠類和子類建立藕合因為它必須通過new關鍵字來創建實例因為這個工廠類很自然地和構造器之間存在藕合同時也和ByteFilter存在藕合(在把值返回給SignalProcessor之前需要創建這個類型的引用)但是工廠類卻不關心ByteFilter的公共方法因為工廠類的概念應該只是創建工廠類創建對象但絕不調用對象的方法
這一切帶來的結果就是當客戶對象或是工廠類需要改變時維護工作的痛苦會減少很多
如果具體的子類要改變例如ByteFilter需要添加或移除不同的實現或者某個實現的業務規則發生改變同時只要維護一下ByteFilterFactory的代碼而不需要影響SignalProcessor
如果ByteFilter的接口發生改變添加移除或改變公共方法然後就需要修改一下SignalProcessor但不會影響ByteFilterFactory
我們非常感興趣一個問題客戶對象和工廠類之間有一個很薄弱的環節那就是ByteFilter這個抽象本身而不是它的接口要特別強調這樣一個事實很多資深的設計員都認為正確的抽象是OO設計的關鍵問題即使接口有問題也好過錯誤的抽象概念
實際工作中的觀點
這是否就意味著你的設計中的每一個類都應該有一個工廠類而其它類都必須實例化這個工廠類?當然如果問題很簡單沒什麼變數例如一個普通排序類那就是殺雞用牛刀了
但這就經常會有問題我們永遠不知道將來會發生什麼樣的變化很不幸我們預知變化的能力歷來都不高有一個折衷的辦法是把構造器封裝在自己的類裡面
只要簡單地把構造器聲明為private或是protected然後添加一個靜態方法這靜態方法返回一個實例代碼片段如下
![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481774.jpg)
這裡關鍵的不同在於封裝了構造器通常客戶對象的代碼是這樣寫的mySender = SendergetInstance();而不是mySender = new Sender();通過把構造器聲明為private可以屏蔽後一種做法
初一看這好象沒有什麼意義和普通做的寫法沒有什麼不同但是這樣我們封裝了new這個操作符在C#或是JAVA這樣語言中new是不能被重載的且我們不能控制它的返回類型你通過new後面跟一個類名來指定一個類型而它總是返回這個類型而getInstance方法就不同了它能返回其它類型作為返回值當Sender需要做一點改變時這種價值就很明顯了我們可以把Sender變成一種多態性服務
這裡主要的好處就是當變化來臨的時候客戶對象不需要改變當有很多客戶對象使用這個服務時這就更有價值了因此出於將來的維護的考慮這麼作是非常有必要的
但是這好象違反了概念劃分的原則吧?畢竟這裡Sender即是概念抽象(抽象類)同時又是具體實現(實現了工廠類)是的在眼前看來我們有時要更實用主義一些象這樣允許一些不可預料的變化而不是為了所有可能的變化放置一些不必要的代碼在裡面
在這個例子中Sender的方法getInstance()作了一個簡單的決策(決定那個子類被創建)並且可能一直都是這麼簡單如果問題變復雜我們將會分離出工廠類來負責創建Sender的子類這是否意味著需要修改客戶對象呢把調用靜態方法改為調整工廠類?完全不用我們可以做一個這樣的委托
![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481769.jpg)
當然這是個不錯的選擇如果客戶對象很少並且有些時間來重構我們也可以把客戶對象改為直接調用工廠類但這並不是必要的而且有點鑽牛角尖了你有沒注意到SenderFactory的構造器也被封裝了Sender調用靜態方法getInstance來創建SenderFacroty實例也許有一天工廠類也會變成多態性服務所以把它也封裝起來會更好吧!
另外把工廠類做成Singleton(一種設計模式它能保證一個類只有一個實例並通過一個全局的訪問點訪問這個實例)是很常見的在重構代碼時只需舉手之勞就能把構造的過程封裝成Singleton
結論
在設計中實體這個概念有不同的視角每個視角表現為某一類操作的內聚而內聚為認是設計的傳統美德因為強內聚的實體容易被理解更少的藕合更好的粒度更容易測試
如果我們努力使實體的操作呈現出更清晰的視角我們就改進了內聚性包括狀態的內聚功能的內聚職責的內聚這將給我們帶來很多的好處
劃分使用視角和創建視角可以有效的提升內聚性這麼作也意味有一個內聚的實體負責創建工作一般是工廠類同樣的我們也不需要關心它如果和使用相結合最後實現整體的功能
實際上在我們設計中掌握這些原則意味著用各種使用關系來確定各種工廠類工廠類則專注於實例的創建(可以參考一些知名的創建模式)
在《設計模式精解》一書中Alan Shalloway and James Trott描述了這樣一種觀點根據上下文設計其中他們說一個設計的某些方面形成了一段上下文通過這樣的上下文可以推導出設計的其它部分這是一個比較宏觀的概念這個概念意味著模式在設計分析和實現中所扮演的角色不過論題的關鍵是這樣的
![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481702.jpg)
隨後他們總結一個更有力的設計原則
![](http://tw.wingwit.com/Article/UploadFiles/201311/2013111309481704.jpg)
劃分創建和使用就是有效的支持了這一原則因此也帶來了很多優勢系統的內聚性會更強可擴展性和靈活性會更好維護將會變的相當的簡單
經常會發現一項好的技術會改進另一項好的技術形成強強聯手
例如測試驅動開發期望編寫的類有更好的可測試性(通過前期關注測試)這也意味更強的內聚性更好的粒度等等分離使用和創建的原則可以帶來更優的可測試性你可以分別測試它們你可以放入一個假的對象來消除依賴性使測試工作更容易
設計模式也是這種觀點的支持者其中最值得稱道的是通過抽象隱藏變化和開放封閉原則這些都給我們帶來了很多改善
然而使用對象不需要知道對象的類型(知道的僅僅是抽象)然後另外再關注創建的問題這就產生了工廠類這就很自然把客戶對象從復雜問題中分離出來
我們正在建立自己的專業特點我們搜尋各種有價值的特性原則實踐方法和模式以些形成我們智慧的基礎
尋找這些思想和我們現有的思想集成我們將更成功給我們的客戶公司文化和經濟方面都能增添價值這也能幫助我們更好的溝通和協作建立一個鼓勵創新的團隊氣氛
最後我們所作的工作會更有趣我相信這是一條正確的道路
From:http://tw.wingwit.com/Article/program/net/201311/11628.html