框架和類庫的區別
框架和類庫等概念的出現都是源於人們對複用的渴望。“不要重複發明輪子”,成了軟體界的一句經典名言。從最初的單個函式原始碼的複用,到面向物件中類的複用(通常以類庫的形式體現),再到基於元件程式設計中二進位制元件(.NET中是以IL程式集形式存在的)的複用,人們複用軟體的抽象層次越來越高。現在,框架複用是抽象層次的又一提升,框架的複用不僅僅是功能的複用,更是設計的複用。
1.1 框架與類庫的區別
我們先來簡單說說什麼是類庫(Class Library)?望文生義,類庫就是一些類的集合,只要我們將一些可以複用的類集中放到一個Library中,我們就可以稱其為一個類庫。類庫中的許多元素(如類、結構、介面、列舉、委託等)之間可能有一些關聯,但這些關聯通常用於支援一個類概念或介面概念的完整表達。如果我們從一個更高的視角來審視類庫,可以發現類庫中的一個個“完整的概念”之間是無關的或是關係鬆散的。
再來說框架,框架的第一含義是一個骨架,它封裝了某領域內處理流程的控制邏輯,所以我們經常說框架是一個半成品的應用。由於領域的種類是如此眾多,所以框架必須具有針對性,比如,專門用於解決底層通訊的框架,或專門用於醫療領域的框架。框架中也包含了很多元素,但是這些元素之間關係的緊密程度要遠遠大於類庫中元素之間的關係。框架中的所有元素都為了實現一個共同的目標而相互協作。
沒有一個萬能的框架可以應用於所有種類的領域和應用,框架的目標性非常強,它專注於解決某一特定領域的問題,並致力於為這一特定領域提供通用的解決方案。
框架與類庫的區別主要表現在以下幾個方面:
(1)從結構上說,框架內部是高內聚的,而類庫內部則是相對鬆散的。
(2)框架封裝了處理流程的控制邏輯,而類庫幾乎不涉及任何處理流程和控制邏輯。
正是由於框架對處理流程的控制邏輯進行了封裝,才使得框架成為一個應用的骨架。框架中的處理流程和控制邏輯需要經過精心的設計,因為所有使用了該框架的應用程式都會複用該設計。
(3)框架具有IOC(控制反轉)能力,而類庫沒有。
IOC,即俗稱的好萊塢模式(Don’t call us, we will call you)。對於類庫中的元素來說,通常都是由我們的應用來呼叫它;而框架具有這種能力――在適當的時候呼叫我們應用中的邏輯。這種能力是通過框架擴充套件點(或稱為“插槽”)來做到的――具體的應用通過擴充套件點注入自己的邏輯,而在適當的時候,框架會呼叫這個擴充套件點中已註冊的邏輯。實際上,.NET中的事件(event)釋出、預定機制就是IOC的一個代表性例子。
(4)框架專注於特定領域,而類庫卻是更通用的。
框架著力於一個特定領域的解決方案的完整表達,而類庫幾乎不針對任何特定領域。比如,本書中提到的通訊框架只適用於需要在TCP/UDP基礎上直接構建通訊的應用程式,而像正則表示式這樣的類庫卻可以使用在各種不同的應用中。
(5)框架通常建立在眾多類庫的基礎之上,而類庫一般不會依賴於某框架。
1.2 通用框架與應用框架
如果要對框架進行進一步分類,則可以根據框架針對的領域是否具有通用性而將它們分為通用框架(General Framework)和應用框架(Application Framework)。通用框架可以在不同型別的應用中使用,而應用框架只被使用於某一特定型別的應用中。
比如,ORM框架NHibernate就是一個通用框架,該框架可以用於所有需要解決O/R對映的各種型別的應用中。而某個金融框架則是一個應用框架,它僅僅被用於金融型別的應用中。
可以這麼說,通用框架所解決的是所有型別的應用都關心的“普遍”問題,而應用框架解決的是某一特定型別的應用關心的問題。所以,如果我們需要將某種型別的應用的核心業務邏輯
在實現具體的應用程式時,可以採用一個應用框架與多個通用框架相結合的方式,這樣有利於快速、高質量的應用程式開發。比如,某個金融領域的一個應用,可以採用金融框架作為應用框架來解決與金融業務邏輯相關的問題,採用Nhibernate解決資料訪問,採用ESFramework解決應用中各分散式系統之間的通訊。
下圖描述了類庫、框架和應用之間的層次關係。
當然,一個應用也可以完全不採用任何框架,而是直接從最基礎的底層API(如.NET Framework)開始構建。對於微型的系統,這種方式或許可行。但對於複雜大型的應用,困難度就可想而知了。
1.3 框架之於應用
當一個應用系統選定了框架之後,我們需要做的就是在框架提供擴充套件點的地方新增應用的具體邏輯,也就是使用“血”和“肉”來填充這個骨架從而得到一個“有機體”。
由於框架通常都是在實踐中經過反覆使用和檢驗的,所以質量有一定的保證,這使得我們用更少的時間、更少的編碼來實現一個更穩定的系統成為可能。當然,框架也不是“銀彈”,它不能解決軟體複雜性的根本問題,但是我們卻通過它向這個終極的理想目標又邁進了一步。
有一點需要注意,框架使得我們的系統在有所支撐的同時,它也給出了限制。因為通常當我們確定採用了某一個框架之後,我們就必須在這個框架限制的“框框”之內來構建我們的應用。大多數時候,這不是一個問題,但是如果因為框架的限制而嚴重影響了我們系統目標的實現的時候,我們就需要考慮是否應該放棄這個框架,或者換一個其它的同類型的框架。
1.4 框架設計
框架使得我們開發應用的速度更快、質量更高、成本更低,這些好處是不言而喻的。然而,面對萬千變化日趨複雜的軟體需求,設計和實現一個高度靈活可複用的框架又談何容易!
框架源於應用,卻又高於應用。
框架往往是這樣產生的:我們擁有了開發某種型別應用的大量經驗,我們總結這種型別的應用中共性的東西,將其提煉到一個高的層次中,以備複用。這個“高層次”的東西便是框架的原型。隨著我們經驗的不斷積累,框架也會不斷地完善、發展。
框架是一個實踐的產物,而不是在實驗室中理論研究出來的。所以設計一個框架最好的方法就是從一個具體的應用開始,以提供同一型別應用的通用解決方案為目標,不斷地從具體應用中提煉、萃取框架!然後在應用中使用這個框架,並在使用的過程中不斷地修正和完善。
有一點需要特別注意,正如所有的軟體架構設計的要點在於權衡(在這方面有點像藝術),框架的設計也不例外,正如前面提到,框架在為應用提供了一個骨架的同時,也給我們的應用圈定了一個框框,我們只能在這個有限的天地內來發揮。所以,一個好的框架設計應當採用了一個非常恰當的權衡決策,以使框架在為我們應用提供強大支援的同時,而又對我們的應用作更少的限制。權衡,從來就不是一件簡單的事情,但是有很多框架設計的經驗可以供我們參考。
1.4.1 框架設計經驗、原則
(1)框架不要為應用做過多的假設!
關於框架為應用做過多的假設,一個非常具體的現象就是,框架越俎代庖,把本來是應用要做的事情攬過來自己做。這是一種典型的吃力不討好的做法。框架越俎代庖,也許會使得某一個具體應用的開發變得簡單,卻會給其它更多想使用該框架的應用增加了本沒有必要的束縛和負擔。
(2)使用介面,保證框架提供的所有重要實現都是可以被替換的。
框架終究不是應用,所以框架無法考慮所有應用的具體情況,保證所有重要的元件的實現都是可以被替換的,這一點非常重要,它使得應用可以根據當前的實際情況來替換掉框架提供的部分元件的預設實現。使用介面來定義框架中各個元件及元件間的聯絡,將提高框架的可複用性。
(3)框架應當簡潔、一致、且目標集中。
框架應當簡潔,不要包含那些對框架目標來說無關緊要的東西,保證框架中的每個元件的存在都是為了支援框架目標的實現。包含過多無謂的元素(類、介面、列舉等),會使框架變得難以理解,嘗試將這些對於框架核心目標不太重要的元素轉移到類庫中,可以使得框架更清晰、目標更集中。
(4)提供一個常用的骨架,但是不要固定骨架的結構,使骨架也是可以組裝的。
比如說,如果是針對某種業務處理的框架,那麼框架不應該只提供一套不可變更的業務處理流程,而是應該將處理流程“單步”化,使得各個步驟是可以重新組裝的,如此一來,應用便可以根據實際情況來改變框架預設的處理流程。這種框架的可定製化能力可以極大地提高框架的可複用性。
(5)不斷地重構框架。
如果說設計和實現一個高質量的框架有什麼祕訣?答案只有一個,重構、不斷地重構。重構框架的實現程式碼、甚至重構框架的設計。重構的驅動力源於幾個方面,比如對要解決的本質問題有了更清晰準備的認識,在使用框架的時候發現某些元件職責不明確、難以使用,框架的層次結構不夠清晰等。
1.4.2 如何稱得上一個優秀的框架?
一個優秀框架的最主要的特點是:簡單。這種簡單性不是輕而易舉就可以獲得的,正如優秀的框架不是一蹴而就的,達到這種簡單性需要對框架不斷地抽絲、不斷地提煉和完善。簡單的真正原因在於它抓住了要解決的問題的本質。一個優秀的框架通常都具有如下特點:
(1)清晰的、簡潔的、一致的。
“清晰”指的是框架的結構是清晰的、框架的層次是清晰明朗的、框架中各個類和元件的職責是清晰明確的。
“簡潔”指的是框架中沒有無關緊要多餘的元素,而且各個類和元件的職責目標是非常集中的,這正是“高內聚、低耦合”設計原則的體現。
“一致”通常會帶來這樣的好處,框架的使用者在熟悉了框架的一部分後,會非常容易地理解框架的另一部分。“一致”通常體現在命名的規則一致、命名的含義一致、元件的裝配方式和使用方式一致等。
(2)易於使用的
只有易於使用的框架才會走得更遠。
正是因為易於使用,框架使用者們才有可能試用這個框架,在試用滿意後才有可能決定採用這個框架。一個框架功能即使再強大,如果難以使用,那麼框架使用者們很可能根本就不會有試用這個框架的念頭。
框架的生命力源於框架一直在不斷地完善和發展,如果沒有人使用這個框架,這個框架便沒有了發展和完善的源動力。正如友好的使用者介面是優秀應用程式不可或缺的重要部分,易於使用也是優秀框架的一個重要特性。
(3)高度可擴充套件的、靈活的
框架通過高度可擴充套件性來應對應用程式的萬千變化。
沒有任何一個框架可以預料所有應用的需求,萬能的框架是不存在的。企圖設計、實現一個萬能框架的想法是荒誕的。框架必須具有“以不變應萬變”的能力,框架可以通過為應用預留恰當的、足夠的擴充套件點來做到這一點。
框架的靈活體現在框架可以根據不同的應用進行不同的組裝和配置,就像框架是專門為當前的應用所訂製的一樣。
(4)輕量的
“輕量”,說的通俗點,就是隻為自己需要使用的服務付費,而不需要為自己不需要的服務買單。一個重量級的框架有一個很明顯的特徵就是,如果你需要一套完整的套餐服務,那是沒有問題的,框架可以很好的滿足你;但是,如果你只需要這份套餐中的一小塊點心,對不起,框架仍然會強加一個完整的套餐給你,你必須付一整份套餐的費用。
優秀的框架應當支援使用者“按需所取”的原則,框架使用者可以隨意“點菜”進行組裝來滿足自己的需求。
(5)弱侵入性的
所謂“弱侵入性”,採用了框架的應用程式可以儘可能的以普通的方式來編寫應用邏輯,而不必為了適應框架不得不使用一些特殊的手法。
這可能有點難以理解,我們可以舉個例子來簡單說明。在.NET中,實現AOP(面向方面程式設計)機制的兩種主要方式是使用Proxy和動態代理。使用Proxy實現的AOP框架通常要求那些需要使用AOP截獲功能的類必須繼承自ContexBoundObject;而採用動態代理實現的AOP框架則沒有任何如此侵入性的要求,我們仍可以以最普通的方式來編寫應用邏輯類,這類框架會在執行時根據配置動態地生成目標物件的代理物件來實現AOP截獲。所以我們可以說,採用動態代理方式實現的AOP框架相比採用Proxy實現的AOP框架,具有更弱的侵入性。
弱侵入性意味著框架對應用邏輯的干擾更少,由於應用邏輯類都是普通的類,這非常方便應用邏輯在另外一個程式中複用,而另外的程式可能採用了一個完全不同的框架。