《Struts2技術內幕》 新書部分篇章連載(三)—— 框架的本質

第2章 固本清源 —— Web開發淺談 

http://downpour.iteye.com/blog/1335948

2.2 框架的本質 

什麼是框架?框架從何而來?爲什麼要使用框架?這是一系列簡單而又複雜的問題。簡單,是因爲它們本身似乎不應該成爲問題。框架實實在在存在,並且在開發中發揮着重要的作用,我們的日常工作,遵循着框架所規定的編程模式,在其指導之下,我們能夠編寫更爲強大的程序。說其複雜,是因爲框架本身又是如此紛繁複雜,我們在使用框架的同時,往往會迷失其中。 

任何事物都有蘊含在其內部的本質。無論框架本身有多複雜,我們所需要探尋的,都是其最爲內在的東西。框架爲什麼會產生?我們來看一個最最簡單的例子。 

在Java中,如果我們要判定一個輸入是否爲null或空字符串,我們會使用下面的代碼: 

Java代碼  收藏代碼
  1. if(str == null || str.length() == 0) {  
  2.     // 在這裏添加你的邏輯  
  3. }  


這段代碼非常普通,一個簡單學習過Java語法的程序員都能夠讀懂並編寫。那麼這段代碼是如何運作的呢?我們所編寫的Java程序,首先獲得的是來自於Java的基本支持:語法支持與基本功能的API級別的支持(str.length()方法實際上就是JDK所提供的字符串的基本API)。換句話說,我們編寫的所有程序,都依賴於一個最最基本的前提條件:JDK所提供的API支持。 

當一個需求被重複1000次,那麼我們就需要重複1000次針對需求的解決辦法,這是一個顯而易見的道理。然而當上面的代碼片段散落在我們的程序中1000次,我們不免會思考,是不是有什麼簡單有效的途徑可以把事情做得更加漂亮一些呢?我們可以針對代碼片段做了一次簡單的邏輯抽取重構,如代碼清單2-4所示: 

Java代碼  收藏代碼
  1. // 定義一個類和一個靜態工具方法來抽象出將被重複調用的邏輯  
  2. public abstract class StringUtils {  
  3.     // 封裝了一個靜態方法  
  4.     public static boolean isEmpty(String str) {  
  5.         return str == null || str.length() == 0;  
  6.     }  
  7. }  
  8.   
  9. // 引用靜態方法取代之前的代碼片段  
  10. if(StringUtils.isEmpty(string)) {  
  11.     // 在這裏添加你的邏輯  
  12. }  


在上面的代碼段中,我們定義了一個靜態方法,將之前寫的那段邏輯封裝了起來。這一層小小的封裝雖然看上去是一個“換湯不換藥”的做法,但是從深遠意義上來說,我們至少可以從以下兩個方面獲得好處: 

可讀性 

靜態方法的簽名從一個角度向我們揭示了一段邏輯的實際意義。比如在這個例子中,isEmpty表示“判定某個輸入是否爲空”。與之前的代碼片段相比,如果我們在一個1000行的程序代碼片段中觀察這2種不同的代碼形式,那麼前者往往會被淹沒在你的視線中,完全無法引起你的思維停頓,而後者卻能夠顯而易見地在邏輯上給你足夠且直觀的提示。 

可擴展性 

如果我們對上述需求稍作改動,程序同時需要對輸入爲空格的字符串做出同樣的判定。我們同樣將上述的需求應用1000次,那麼前者將導致我們在整個應用中進行搜索並替換修改1000次,而後者只需要針對我們封裝的邏輯修改1次即可。 

從上面的例子中我們可以看出,雖然僅僅對代碼做了一次簡單的重構,卻在上述的兩個方面爲我們解決了潛在的問題。這一現象或許直到現在才被你意識到,但它卻很早以前就被程序屆的很多前輩意識到了。因而,早就有人爲此編寫了類似的代碼。比如說,類似的方法就存在於Apache的commons-lang的JAR包中,如代碼清單2-5所示: 

Java代碼  收藏代碼
  1. package org.apache.commons.lang;  
  2.   
  3. public class StringUtils {  
  4.       
  5. // 這裏省略了許多其他的代碼  
  6.       
  7. public static boolean isEmpty(String str) {  
  8.         return str == null || str.length() == 0;  
  9. }  
  10.   
  11. }  


當我們將Apache的commons-lang的JAR包加到CLASSPATH中時,我們就能在程序的任何地方“免費地”使用上述方法。也就是說,我們自己無需自行編寫代碼對JDK進行擴展了,因爲Apache的commons-lang已經爲我們做了。既然如此,我們唯一所需要做的,只是把別人做的東西加到CLASSPATH中並且使用它而已。 

這是一個很熟悉的過程,不是嗎?我們在搭建程序運行的基本環境時,指定程序所依賴的JAR文件是其中的一個重要步驟。而這一步驟,實際上包含了Java開發中最最基本而淺顯的道理: 

downpour 寫道
結論 當我們加載一個JAR包到CLASSPATH時,實際上是獲得了JAR中所有對JDK的額外支持。


我們的程序就像一個金字塔形狀。位於最底部的當然是JVM,提供了運行Java程序的基礎環境,包括對整個Java程序的編譯運行。在這個之上的是JDK,JDK則是構建在JVM之上的基本的對象行爲的定義(我們在搭建開發環境時所安裝的JDK就是這個)。而再往上,是一個具備層次結構的JAR層,所有被加載到CLASSPATH中的JAR文件都被搭建在JDK層次之上,它們之間可能形成互相依賴,但不管怎麼說,它們的作用都是提供JDK以外的功能支持。最後,在金字塔尖的,纔是我們日常編寫的應用程序,它將依賴於金字塔低端的所有程序。這樣一個結構,就如同圖2-3所示的那樣: 



仔細觀察一下這個處於中間的JAR層,這個層次的組成結構與其他的層次有着不同。它是由一塊塊磚頭堆砌而成,上層的磚塊搭建在下層的磚塊之上。如果我們把其中的每一塊磚都比作一個JAR文件,它們之間也就形成了明顯的具備層次的依賴關係。 

這個層次中的任何JAR文件本身可能並不爲最終的程序提供具體的功能實現,但是它卻爲我們編寫程序提供了必要的支持。如果查看一個標準的J2EE程序運行時所依賴的CLASSPATH中的JAR包,會發現我們所熟悉的那些“框架”,實際上都蘊含其中。我們在這裏給出一個最簡單的示例程序在Eclipse中的CLASSPATH截圖,如下圖2-4所示: 

 

從圖中我們看到,JRE System Library是整個應用程序最基本的運行環境。而無論是Struts2還是Spring,他們都以JAR文件的形式被加載到程序運行所依賴的CLASSPATH中去,併爲我們的應用程序使用。如果我們用更加通俗的話來表述這一現象: 

downpour 寫道
結論 框架只是一個JAR包而已,其本質是對JDK的功能擴展。


當我們說一個程序使用了Spring框架,隱藏在背後的潛臺詞實際上是說,我們把Spring的分發包加入到了CLASSPATH,並且在程序中使用了其功能。框架,其實就是這麼回事!就是如此簡單的東西! 

到現在爲止,框架似乎還沒有任何在我們的知識範疇以外的東西,它們的本質是如此一致,以至於我們很容易遺忘把一個JAR文件加入到CLASSPATH中的初衷:解決在某個領域的開發中所碰到的困境。正如我們在一開始使用的那個例子一樣,框架作爲一個JAR包,實際上是許許多多解決各種問題的類和方法的集合。當然,更多時候,它們包含了編寫這些JAR包的作者所創造的許多最佳實踐。 

downpour 寫道
結論 框架是一組程序的集合,包含了一系列的最佳實踐,作用是解決某個領域的問題。


因此只有解決問題纔是所有框架的共同目標框架的產生就是爲了解決一個又一個在開發中所遇到的困境。不同的框架,只是爲了解決不同領域的問題。所以,對於廣大程序員來說,千萬不要爲了學習框架而學習框架,而是要爲了解決問題而學習框架,這纔是一個程序員的正確學習之道。 

2.3 最佳實踐 

一切程序的編寫,都需要遵循特定的規範。這裏所說的規範,往往是建立在運行環境之上的一系列概念和實現方法的基本定義,並歸納爲一個完整的體系。例如,我們使用Java來進行Web開發,所需要遵循的最基本的規範就是我們所熟悉的Servlet標準、JSP標準,等等。 

建立在標準和規範之上的,是各種各樣的針對這些標準和規範的實現。這些實現構成了程序運行的基本環境。例如,Tomcat有對Servlet標準的實現方式,而Websphere則有不同的實現方式。然而它們在本質上都實現了Servlet標準所規定的接口,從而讓我們的應用程序可以透明地使用這些API,而無需關心真正的Web容器內部的實現機理。 

我們所編寫的程序,總是建立在一系列的規範和基本運行環境之上。面對紛繁複雜的業務需求,不同的程序員可以按照自己的意願來編寫程序,因此,即使爲了表達相同的業務功能,不同的程序代碼之間的差異性也是很大的。程序的差異性有時候會給人以創新的靈感,但是更多的時候會造成麻煩。因爲差異性越大,維護起來就越麻煩。出於對可維護性和可讀性的要求,我們所希望的程序最好能從宏觀層面上看上去是一致的,使得每一個程序員都能夠讀懂併合理運用,這纔是我們的目標。這一目標,我們習慣上稱之爲最佳實踐。 

downpour 寫道
結論 最佳實踐(Best Practice),實際上是無數程序員在經過了無數次的嘗試後,總結出來的處理特定問題的特定方法。如果我們把每個程序員的自由發揮看作是一條通往成功的路徑,最佳實踐就是其中的最短路徑,它能夠極大地解放生產力。


所有的這些最佳實踐,最終又以一個個JAR包的形式被蘊含在框架之中,對我們的應用程序提供必要的支持,因此我們有必要在這裏探尋一些最最基本的最佳實踐,從而能夠從更加深的層次瞭解框架存在的意義和框架的設計初衷。在之後的章節中,我們會反覆提及這些最佳實踐,因爲它們不僅能夠指導我們進行程序開發,它們本身也被蘊含在了Struts2的內部。 

downpour 寫道
最佳實踐 永遠不要生搬硬套任何最佳實踐,真理之鎖永遠只爲最合適的那把鑰匙開啓。


這是一條凌駕於任何最佳實踐之上的最佳實踐。在我們使用框架編寫程序時,程序員最容易犯的毛病就是對某項技術或者某個框架的絕對迷信,並將它生搬硬套於任何程序開發之中。應用程序永遠服務於具體的業務場景,對於不同的業務需求,我們的解決方案也會有所區別,自然也會涉及到不同的框架選擇。 

在實際開發中,我們遇到的許多編程開發的問題都是沒有固定答案的哲學取向問題。所以,往往沒有“最好”的答案,只有“最合適”的答案。這也將成爲日後在面對多種解決方案進行取捨時的一個基本準繩。 

downpour 寫道
最佳實踐 始終保證程序的可讀性、可維護性和擴展性。


可讀性、可維護性和擴展性,就像三角架的三個支撐腳,缺一不可任何對程序的重構,實際上都圍繞着這三個基本原則進行,而它們也同時成爲衡量程序寫得好壞的最基本標準。代碼的不斷重構、框架的產生實際上都來自於這三個程序內在屬性的驅動。 

我們之前已經反覆提到了程序的可維護性和擴展性。事實上,程序的可讀性也是程序所應具備的必不可少的基本屬性,失去了可讀性的程序,其可維護性和擴展性也就無從談起了。這三大原則從方法論的角度規定了一切最佳實踐都不能違背這三大程序的基本屬性。否則,我們遲早會爲一些蠅頭小利而捨棄程序開發的源頭根本。當一個程序失去了可讀性、可維護性和擴展性,它也就失去了生命力。 

downpour 寫道
最佳實踐 簡單是美(Simple is Beauty)


簡單是美(Simple is Beauty)是一種指導思想,它其實包含了兩個層次的意思。第一層意思是消除重複(Don’t repeat yourself),這是一個顯而易見的代碼重構標準。第二層意思則是要求我們化繁入簡(Heavy to Light),用盡量少的代碼語言來表達儘量多的邏輯意義。 

簡單是美,將最佳實踐的要求細化到了方法論的層面。然而,無論我們的程序如何簡單,都應該始終記得,簡單必須可讀,簡單必須可擴展切忌爲了一些細節,而忘記了更大的原則。 

downpour 寫道
最佳實踐 儘可能使用面向對象的觀點進行編程。


我們可以看到,這個層面的最佳實踐,已經從基本準則和指導思想轉向了具體的編程層面。雖然面向對象自身也只是一種編程的指導思想,然而它卻是和程序設計和實現息息相關並且對程序編寫影響最大的一條編程準則。 

面向對象這個概念本身就是一個非常耐人尋味的問題。談到面向對象的概念、設計和方法論,恐怕一天一夜都講不完。在本章之初,我們從“對象”這個概念入手,通過對“對象”內部結構的分析,試圖向讀者展示面向對象編程中的一些重要理論。讀者對這些理論不應停留在死記硬背的層面,而是要將他們融入到框架的設計理念中去理解。同時,這些理論也將成爲我們判別框架和設計方案優劣的重要標準。 

downpour 寫道
最佳實踐 減少依賴(消除耦合)。


之前我們在分析框架的本質時已經提到,任何Java程序總是依賴於其運行環境(JVM層)和支持應用程序的JAR層。加入到CLASSPATH中JAR越多,就意味着程序對外部環境的依賴度越高,對外部環境的依賴度越高,就意味着程序本身越難以脫離特定的外部環境進行單元測試。因此,減少甚至消除依賴,也成爲了許多框架所追求的目標。 

Struts2在這一點上做得尤爲成功。Struts2不但實現了Web框架與Web容器之間的解耦合,還在此基礎之上實現了各個編程元素之間的有效溝通。在之後的章節中,我們會深入探究Struts2在這條最佳實踐上所做出的努力。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章