官术网_书友最值得收藏!

第2章 固本清源—Web開發(fā)淺談

現(xiàn)今,在談到Web開發(fā)有關(guān)的話題時,程序員們總是熱衷于討論一些我們耳熟能詳?shù)腤eb開發(fā)框架,如Struts2、Spring、Hibernate等。有些程序員將這些框架奉為寶典,并且趨之若鶩地挖掘框架的方方面面、比較各種開發(fā)框架的優(yōu)劣。對這些框架的熟悉與否,似乎已成為衡量一個程序員是否精通Java、精通J2EE開發(fā)的事實標準。甚至在廣大程序員求職的過程中,這些主流的開發(fā)框架的知識細節(jié)也常常成為面試中必考的元素,答不上這些問題,無疑會為求職蒙上一層陰影。

面對這些框架,大家是否真的思考過,我們?yōu)槭裁匆獙W(xué)習(xí)這些框架?這些框架到底從何而來?框架的本質(zhì)到底是什么?使用框架,又能夠為我們的開發(fā)帶來什么樣的好處呢?在深入分析Struts2及其源碼之前,我們首先必須弄清楚這些比框架更為核心的問題。因為只有了解了為什么,我們才能知道怎么做,知道如何才能做得更好。

2.1 面向?qū)ο鬁\談

在談框架之前,我們不得不首先面對一個比框架更為重要的概念,那就是面向?qū)ο?/span>的概念。面向?qū)ο蟮母拍钍且粋€看起來、聽起來簡單,實際卻蘊含著豐富內(nèi)容的概念。眾多的國內(nèi)外學(xué)者為了講清楚這個概念,采用了各種的不同的比喻、也給出了多種多樣的代碼示例,還為面向?qū)ο蟮母拍罱⑵鹨惶淄暾睦碚擉w系。

不過迄今為止,能夠完整地將面向?qū)ο蟮膩睚埲ッ}講清楚、講透徹的畢竟還是少數(shù)。隨著編程語言從早期的“面向過程式”的C語言發(fā)展到后來的C++、Java,直至近幾年來非常熱門的Ruby,面向?qū)ο蟮幕靖拍钜呀?jīng)逐漸成為編程語言的核心設(shè)計法則。因而“面向?qū)ο蟆钡母拍钜仓饾u成為每個程序員都認同,并且在日常編程過程中遵循的最高綱領(lǐng)。

限于篇幅,我們實在無法涉及面向?qū)ο蟾拍畹姆椒矫婷妗2贿^我們可以將話題聚焦在構(gòu)成面向?qū)ο蟾拍钭罨镜脑刂希@個基本元素就是:對象。在接下來的章節(jié)中,我們將分析對象的構(gòu)成模型以及對象的關(guān)系模型,并以此為基礎(chǔ)闡述在面向?qū)ο缶幊踢^程中的一些基本觀點。

2.1.1 對象構(gòu)成模型

2.1.1.1 對象的構(gòu)成分析

作為面向?qū)ο缶幊套罨镜臉?gòu)成元素,對象是由一個叫做類(Class)的概念來描述的。因此,針對對象構(gòu)成分析的研究,也就轉(zhuǎn)化為針對編程語言中類的構(gòu)成分析。以Java語言為例,我們可以對Java語言中類的定義進行一些構(gòu)成上的分析,如圖2-1所示。

圖2-1 對象的構(gòu)成分析

在圖中,我們可以看到構(gòu)成一個對象的基本要素主要有:

簽名(Signature)—對象的核心語義概括

屬性(Property)—對象的內(nèi)部特征和狀態(tài)的描述

方法(Method)—對象的行為特征的描述

在進行面向?qū)ο蟮木幊虝r,首先要做的就是對世間萬物進行編程元素的抽象。這個過程說白了,就是通過使用編程語言所規(guī)定的語法,例如類(Class)或者接口(Interface)來表達事物的邏輯語義。在圖2-1中我們所談到的構(gòu)成一個對象定義的基本要素,實際上不僅反映出我們對世間萬物的抽象過程,也是人類使用高級編程語言來實現(xiàn)外部世界表述的基本方式。

從圖2-1中我們可以看到,簽名用以描述事物的核心語義,它的作用實際上是界定我們所描述的事物的范疇。而在對象的內(nèi)部,作為對象內(nèi)部構(gòu)成的重要元素,屬性方法剛好從兩個不同的角度對事物的內(nèi)在特性給予了詮釋。其中,屬性所勾勒的是一個對象的構(gòu)成特性和內(nèi)部狀態(tài)的特性;方法則表達了一個對象的動態(tài)行為特性。這就像我們?nèi)艘粯樱擞深^、軀干、四肢構(gòu)成,它們可以看作是人這個對象的“屬性”。與此同時,人具有“直立行走”的行為特性,我們可以定義一個“方法”來模擬這一行為。

以上這些分析,我們還停留在語法這個層面,因為無論是屬性還是方法,它們都是Java語言的原生語法支持。將事物抽象成為對象,并且賦予這個對象屬性和方法,是一個很自然的編程邏輯,這也符合面向?qū)ο缶幊陶Z言的基本思路。不過我們也同時發(fā)現(xiàn)在實際編程過程中,對象將表現(xiàn)為三種不同的形態(tài)和運作模式。

屬性--行為模式

這種模式是指一個對象同時擁有屬性定義和方法定義。這是對象最為普遍的一種運行模式,絕大多數(shù)對象都運作在這種模式之上。

屬性模式

這種模式是指一個對象只擁有屬性定義,輔之以相應(yīng)的setter和getter方法。Java規(guī)范為運行在這種模式下的對象取了一個統(tǒng)一的名稱:JavaBean。JavaBean從其表現(xiàn)出來的特性看,可以作為數(shù)據(jù)的存儲模式和數(shù)據(jù)的傳輸載體。

行為模式

這種模式是指構(gòu)成一個對象的主體是一系列方法的定義,而并不含有具體的屬性定義,或者說即使含有一些屬性定義,也是一些無狀態(tài)的協(xié)作對象。運行在這種模式之下的對象,我們往往稱之為“無狀態(tài)對象”,其中最為常見的例子是我們熟悉的Servlet對象。

我們發(fā)現(xiàn),對象的運行模式的劃分是根據(jù)對象的構(gòu)成特點進行的。這三種對象的運行模式在我們?nèi)粘>幊讨卸家呀?jīng)見過并且親自實踐過。接下來的章節(jié),我們將針對后兩種構(gòu)成模式做進一步的分析。

2.1.1.2 屬性對象模式

屬性對象模式又稱為JavaBean模式。這種對象的運行模式我們在日常編程中見得非常多。作為數(shù)據(jù)存儲和數(shù)據(jù)傳輸?shù)妮d體,運行在JavaBean模式下的對象,在眾多的編程層次都會被用到,并且根據(jù)作用不同被冠以各種不同的名稱,如

PO(Persistent Object)—持久化對象

BO(Business Object)—業(yè)務(wù)對象

VO(Value Object)—值對象

DTO(Data Transfer Object)—數(shù)據(jù)傳輸對象

FormBean—頁面對象

對于這些紛繁復(fù)雜的縮寫和對象類別,許多初學(xué)者會感到非常頭疼。它們從形式上看是一系列難記的縮寫,不過真正讓程序員頭疼的,不僅在于它們被用于不同的業(yè)務(wù)場景和編程層次,還在于它們在某些時候甚至只是同一個對象在不同層次上的不同名稱。

不過我們大可不必在對象的名稱和叫法上過分糾結(jié)。因為對于程序員來說,無論這些對象被冠以什么花里胡哨的名稱,它們只不過是對基本的、運行在JavaBean模式下對象的有效擴展或增強。

以PO(Persistent Object)為例,當我們使用Hibernate作為O/R Mapping的工具時,一個典型的PO會被定義成如代碼清單2-1所示的樣子。

代碼清單2-1 User.java

          @Entity
          @Proxy(lazy = true)
          @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
          public class User {
              @Id
              @GeneratedValue
              private Integer id;
              @Column
              private String name;
              @Column
              private String password;
              @Column
              private String email;
              // 這里省略了所有的setter和getter方法
          }

假設(shè)去除那些Annotation,我們會發(fā)現(xiàn)這個PO和一個普通的JavaBean并無不同,至少我們無法在形式上將它們區(qū)分開。因此,我們說Annotation在這里的所用是豐富了一個普通JavaBean的語義,從而使之成為一個持久化對象。而當我們使用O/R Mapping的工具Hibernate進行處理時,也是根據(jù)這些Annotation才能夠?qū)@些PO進行識別并賦予其相應(yīng)功能的。也就是說,JavaBean自身的特性并沒有發(fā)生改變,只是引入了一些額外的編程元素從而對JavaBean進行了增強。

當一個對象運作在屬性對象模式時,其本質(zhì)是對象的JavaBean特性。我們可以從其表現(xiàn)形式和運行特征中得出下面這樣的一個結(jié)論。

結(jié)論 JavaBean對象的產(chǎn)生主要是為了強調(diào)對象的內(nèi)在特性和狀態(tài),同時構(gòu)造一個數(shù)據(jù)存儲和數(shù)據(jù)傳輸?shù)妮d體。

我們在本節(jié)開篇所提到的各種不同的對象名稱的定義,它們之間的界定實際上是非常模糊的。同樣一個對象,往往可以兼任多種不同的角色,在不同的編程層次表現(xiàn)為不同的對象實體,其最終目的是在特定的場合表現(xiàn)出其作用。

在上面的結(jié)論中,我們還讀到另外一層意思,那就是JavaBean對象的一個重要的性質(zhì)在于它是一個數(shù)據(jù)存儲和數(shù)據(jù)傳輸?shù)妮d體。有關(guān)這一點,我們將在之后的章節(jié)進行分析。

2.1.1.3 行為對象模式

根據(jù)之前有關(guān)對象構(gòu)成的分析,運行在行為對象模式之下的對象,我們往往稱之為無狀態(tài)對象。在上一節(jié)中,我們提到對象的內(nèi)在特性和狀態(tài)是由構(gòu)成對象的屬性來表達的。所以,所謂的無狀態(tài)對象實際上就是指對象的方法所表達的行為特性并不依賴于對象的內(nèi)部屬性的狀態(tài)。而這種無狀態(tài)的特性,非常適合進行一個請求的響應(yīng),并在方法體的內(nèi)部實現(xiàn)中進行復(fù)雜的業(yè)務(wù)邏輯處理。

我們在這里可以針對對象的行為方法的語法做進一步的分析,如圖2-2所示。

圖2-2 對象的行為方法的語法分析

從圖2-2中,我們看到對象方法的語法定義同樣體現(xiàn)出一定的規(guī)律性:

方法簽名(Signature)—行為動作的邏輯語義概括

參數(shù)(Parameter)—行為動作的邏輯請求輸入

返回值(Return)—行為動作的處理響應(yīng)結(jié)果輸出

我們可以看到,方法的定義實際上是一種觸發(fā)式的邏輯定義。當需要完成某種行為動作(業(yè)務(wù)邏輯)時,我們會將請求作為參數(shù)輸入到方法的參數(shù)列表中,而返回值則自然而然地成為業(yè)務(wù)邏輯的處理結(jié)果。由此,我們可以得出以下結(jié)論:

結(jié)論 對象中的方法定義是進行請求響應(yīng)的天然載體。

這個結(jié)論我們將在之后對各種Web框架對Http請求的響應(yīng)設(shè)計中再次提及。因為在Java標準中,對Http請求的響應(yīng)是通過Servlet標準來實現(xiàn)的。而我們知道,Servlet對象就是一個非常典型的運行在行為對象模式之上的無狀態(tài)對象。

上述結(jié)論對讀者理解“請求-響應(yīng)”的過程在編程語言中的邏輯表達有很大的幫助。因而讀者應(yīng)仔細體會這些構(gòu)成要素在“請求-響應(yīng)”過程中所起的作用,從而對Web框架的設(shè)計有更深的感悟。

2.1.2 對象關(guān)系模型

對象的構(gòu)成模型是從對象內(nèi)部結(jié)構(gòu)的角度對面向?qū)ο缶幊讨械幕驹剡M行的分析。在本節(jié)中,我們分析的角度將由“內(nèi)”轉(zhuǎn)向“外”,考慮對象與對象之間的關(guān)系。

談到對象之間的關(guān)系,我們很容易想到兩個不同的層次:

從屬關(guān)系—一個對象在邏輯語義上隸屬于另外一個對象

協(xié)作關(guān)系—對象之間通過協(xié)作來共同表達一個邏輯語義

這兩種關(guān)系在面向?qū)ο缶幊陶Z言中分別擁有不同的表現(xiàn)形式和邏輯意義,這二者構(gòu)成了絕大多數(shù)的對象關(guān)系模型。接下來,我們就來分別分析這兩種對象關(guān)系模型。

2.1.2.1 對象的從屬關(guān)系

對象的從屬關(guān)系,主要指一個對象在邏輯語義上隸屬于另外一個對象。這個定義實際上依然非常抽象。要理解這一定義,我們就必須從“隸屬”這個核心詞匯入手。邏輯語義上的“隸屬”,主要有兩種不同的含義。

歸屬

歸屬的邏輯含義很直觀。比如說,一個人總是歸屬于一個國家;一本書總是有作者。因而,當我們把人和國家、書和作者都映射成面向?qū)ο缶幊陶Z言中所定義的一個個對象時,它們之間自然而然就形成了歸屬關(guān)系。這種歸屬關(guān)系是由外部世界的邏輯關(guān)系映射到編程元素之上而帶來的。

繼承

繼承的邏輯含義就有點晦澀。比如說,馬、白馬和千里馬之間的關(guān)系。首先,白馬和千里馬都是馬的一種,然而白馬和千里馬卻各自擁有自己獨有的特性:白馬是白色的、千里馬一日可行千里。此時,我們可以說白馬和千里馬都屬于馬,它們都繼承了馬的基本特征,卻又各自擴展了自身獨有的特質(zhì)。

明確了“隸屬”的兩層含義,我們就需要把它們和面向?qū)ο缶幊陶Z言聯(lián)系在一起。歸屬和繼承,它們在面向?qū)ο蟮木幊陶Z言中又以怎樣的形式表現(xiàn)出來呢?

我們先來看看“歸屬”這層含義。對于“歸屬”關(guān)系的編程語言表現(xiàn)形式,我們可以得出下面的結(jié)論:

結(jié)論 “歸屬”關(guān)系在面向?qū)ο缶幊陶Z言中,主要以對象之間互相引用的形式存在。

我們以書和作者之間的對象定義作為例子來說明,其相關(guān)源碼如代碼清單2-2所示。

代碼清單2-2 Book.java

          public class Book {
            private String name;
            private List<Author> authors;
          }

我們在這里所表達的是書和作者之間的“歸屬”關(guān)系。從代碼中,可以很明顯看到,一本書可能有多個作者,所以我們在書(Book)的屬性定義中加入了一個List的容器結(jié)構(gòu),List中的對象類型是作者(Author)。這樣一來,書和作者之間就形成了一個引用關(guān)系。

使用對象之間的引用來表達“歸屬”關(guān)系,是一種非常廉價的做法。因為這種關(guān)系的表達來源于對象定義的基本模式,不會對對象自身產(chǎn)生破壞性影響。

細心的讀者還會發(fā)現(xiàn),我們這里所說的“歸屬”關(guān)系,實際上還蘊含了一層“數(shù)量”的對應(yīng)關(guān)系。在上面的例子中,我們發(fā)現(xiàn)書和作者的數(shù)量關(guān)系是“一對多”。除了“一對多”以外,對象之間的歸屬關(guān)系在數(shù)量這個維度上還可以是“一對一”和“多對多”。有趣的是,這三種歸屬關(guān)系正好也和我們關(guān)系型數(shù)據(jù)庫模型中所定義的基本關(guān)系一一對應(yīng)。這種關(guān)系模型也就為我們在Java世界和數(shù)據(jù)庫世界之間進行O/R Mapping打下了理論基礎(chǔ)。

看完了“歸屬”關(guān)系,我們再來看看“繼承”關(guān)系。有關(guān)“繼承”關(guān)系的編程形式表述,我們可以用下述結(jié)論來進行說明:

結(jié)論 “繼承”關(guān)系在面向?qū)ο缶幊陶Z言中,主要以原生語法的形式獲得支持。

什么是“以原生語法的形式獲得支持”呢?我們來看看之前說的那個白馬的例子,其相關(guān)源碼如代碼清單2-3所示。

代碼清單2-3 Horse.java

          public class Horse {
            public void run() {
            }
          }
          public class WhiteHorse extends Horse {
          }
          public class ThousandMileHouse extends Horse {
          }

白馬和馬之間,我們使用了Java中的關(guān)鍵字extends來表達前者繼承自后者。這種方式與我們之前所看到的對象之間的引用模式完全不同,它使用了編程語言中的原生語法支持。

這種對象關(guān)系的表達方式非常簡單而有效,不過當我們引入一個語法,就不得不遵循這個語法所規(guī)定的編程規(guī)范所帶來的編程限制。例如在Java中,我們就必須遵循單根繼承的規(guī)范。這一點也是“繼承”這種方式經(jīng)常被詬病的原因之一。

在Java中,除了extends關(guān)鍵字以外,還有implements關(guān)鍵字來表達一個實現(xiàn)類與接口類之間的關(guān)系。實際上這是一種特殊的“繼承”關(guān)系,它無論從語義上還是表現(xiàn)形式上,與extends都基本相同。

2.1.2.2 對象的協(xié)作關(guān)系

對象的從屬關(guān)系從現(xiàn)實世界邏輯語義的角度描述了對象與對象之間的關(guān)系。從之前的分析中,我們可以發(fā)現(xiàn)無論是“歸屬”關(guān)系還是“繼承”關(guān)系,它們都在圍繞著對象構(gòu)成要素中的屬性做文章。那么讀者不禁要問,圍繞著對象的行為動作特征,對象之間是否能夠建立起關(guān)系模型呢?

從哲學(xué)的觀點來看,萬事萬物都存在著普遍而必然的聯(lián)系。從對象的行為特性上分析,一個對象的行為特征總是能夠與另外一個對象的行為特征形成依賴關(guān)系。而這種依賴關(guān)系,在極大程度上影響著對象的邏輯行為模式。例如,一個人“行走”這樣一個動作,需要手腳的共同配合才能完成,具體來說就是“擺手”和“抬腳”。而當我們把手和腳分別看作一個對象時,“擺”和“抬”就成為手和腳的行為動作了。

這樣一說,似乎對象之間的協(xié)作關(guān)系就非常容易理解了,請看以下結(jié)論:

結(jié)論 當對象的行為動作需要其他對象的行為動作進行配合時,對象之間就形成了協(xié)作關(guān)系。

可以想象,一個對象在絕大多數(shù)情況下都不是孤立存在的,它總是需要通過與其他對象的協(xié)作來完成其自身的業(yè)務(wù)邏輯。這是軟件大師Martin Fowler曾經(jīng)提過的一個重要觀點。然而這卻為我們的編程帶來了一些潛在的問題:如何來管理對象和協(xié)作對象之間的關(guān)系呢?有關(guān)這一問題,我們將在第5章詳細進行講解。

對象的協(xié)作關(guān)系在對象運行在行為對象模式時顯得尤為突出。因為當使用一個具體的方法來進行動作響應(yīng)時,我們總是會借助一些輔助對象的操作來幫助我們共同完成動作的具體邏輯。也就是說,我們會將一個動作從業(yè)務(wù)上進行邏輯劃分,將不同的業(yè)務(wù)分派到不同的對象之上去執(zhí)行。這就成為我們所熟知的分層開發(fā)模式的理論依據(jù)。

2.1.3 面向?qū)ο缶幊痰幕居^點

在了解了對象的構(gòu)成模型和對象的關(guān)系模型之后,讀者不免要問,這些內(nèi)容和我們的日常編程有關(guān)系嗎?答案是有關(guān)系!而且不僅是有關(guān)系,還是有相當大的關(guān)系!在本節(jié)中,我們就以結(jié)論輔之以分析的方法,為讀者展示面向?qū)ο缶幊讨械囊恍┗居^點。

結(jié)論 每一種對象的構(gòu)成模型,都有其特定的應(yīng)用范圍。

根據(jù)之前我們有關(guān)對象的構(gòu)成模型的分析,可以發(fā)現(xiàn)三種對象的構(gòu)成模型在日常的編程過程中都曾經(jīng)碰到過。因此,我們應(yīng)該首先明確的觀點是每一種對象的構(gòu)成模型都有其存在的合理性,并沒有任何一種模型是錯誤的模型這一說。

既然如此,我們所要做的就是認清這些對象構(gòu)成模式的特性,并且能夠在最恰當?shù)臉I(yè)務(wù)場景中選擇最合適的模型加以應(yīng)用。那么,從面向?qū)ο笏枷氲慕嵌龋绻覀儗⑦@些對象運作模式做一個縱向的比較,它們有沒有優(yōu)劣之分呢?

結(jié)論 將對象運作在“屬性-行為”模式上,最符合面向?qū)ο缶幊趟枷氲谋疽狻?/span>

這一結(jié)論承接了上一個結(jié)論,可以說是對象建模方式的一種合理的理解和擴展,也回答了我們剛才的問題。當我們進行對象建模的時候,總是首先需要根據(jù)業(yè)務(wù)情況選擇一個對象建模設(shè)計的角度,而這個角度往往取決于對象在整個程序中所起的作用。例如,當我們需要進行數(shù)據(jù)傳輸或者數(shù)據(jù)映射時,我們應(yīng)該基于對象的“屬性模式”來進行對象建模;當我們需要進行動作響應(yīng)時,我們應(yīng)該基于對象的“行為模式”來進行對象建模。

然而,運行在“屬性模式”中的對象并不是說完全就不能具備行為動作。基于某一種模式進行建模,只是我們考慮對象設(shè)計的角度不同。如果我們站在一個“對象構(gòu)成的完整性”這樣一個高度來看待每一個對象,它們總是由屬性和方法共同構(gòu)成。因此,在任何一種對象的構(gòu)成模式上走極端都是不符合面向?qū)ο缶幊趟枷氡疽獾摹?/p>

軟件大師Martin Fowler就曾經(jīng)撰文指出,在對象建模時不應(yīng)極端地將對象設(shè)計成單一的“屬性模式”。讀者可以參考:http://www.martinfowler.com/bliki/AnemicDomainModel.html獲得全文描述。

The fundamental horror of this anti-pattern is that it's so contrary to the basic idea

of object-oriented design; which is to combine data and process together.

有關(guān)這一點,也引起了許多國內(nèi)軟件開發(fā)人員的深入討論,并且引申出許多極具特色的名詞,諸如:“貧血模型”、“失血模型”、“充血模型”、“脹血模型”等等。這些討論非常有價值,對于對象建模有興趣的讀者可以使用搜索引擎就相關(guān)的討論進行搜索。

既然存在著那么多有關(guān)“領(lǐng)域模型”的理解方式,為什么Martin Fowler這樣的軟件大師還是推薦盡可能使對象運行在“屬性-行為”模式之上呢?除了它自身在構(gòu)成形式上比較完整,能夠比其他兩種運行方式更容易表達對象的邏輯語義之外,還有什么別的特殊考慮嗎?筆者通過思考和分析,給出可能的兩個理由:

當對象運作在“屬性--行為”模式上時,我們能夠最大程度地應(yīng)用各種設(shè)計模式

對于設(shè)計模式有深入研究的讀者,應(yīng)該會同意這個觀點。設(shè)計模式的存在基礎(chǔ)是對象,因而設(shè)計模式自身的分類也圍繞著對象展開。我們可以發(fā)現(xiàn),絕大多數(shù)的設(shè)計模式需要通過類、接口、屬性、方法這些語法元素的共同配合才能完成。因而,單一的屬性模式和行為模式的對象,在設(shè)計模式的應(yīng)用上很難施展拳腳。

當對象運作在“屬性--行為”模式上時,我們能夠最大程度地發(fā)揮對象之間的協(xié)作能力

仔細分析對象的關(guān)系模型,我們會發(fā)現(xiàn)無論是對象的從屬關(guān)系還是對象的協(xié)作關(guān)系,它們在絕大多數(shù)情況下是通過對象之間的屬性引用來完成的。這種屬性引用的方式,只是在形式上解決了對象和對象之間進行關(guān)聯(lián)的問題。而真正談到對象之間的配合,則不可避免地需要通過行為動作的邏輯調(diào)用來完成,這也是對象協(xié)作的本質(zhì)內(nèi)容。

對象建模是一個很深刻的哲學(xué)問題,它將直接影響我們的編程模式。所以對于建模這個問題,大家應(yīng)該綜合各家之言,并形成自己的觀點。筆者在這里的觀點是:對象建模方式首先是一個哲學(xué)問題,取決于設(shè)計者本身對于業(yè)務(wù)和技術(shù)的綜合考慮。任何一種建模方式都不是絕對正確或者絕對錯誤的方式。我們所期待看到的對象建模的結(jié)果是提高程序的“可讀性”、“可維護性”和“可擴展性”。一切建模方式都應(yīng)該首先服務(wù)于這一基本的程序開發(fā)的最佳實踐。

結(jié)論 建立對象之間的關(guān)系模型是面向?qū)ο缶幊痰暮诵膬?nèi)容。

對象建模是一個很復(fù)雜的邏輯抽象過程。事實上,對象建模最難的地方并不在于設(shè)計某一個單體對象的屬性構(gòu)成或者方法構(gòu)成。因為之前我們也提到,對象總不能以單體的形式孤立存在。對象與對象之間總是以某種方式相互關(guān)聯(lián)、相互配合。這種關(guān)聯(lián)要么形成對象之間的從屬關(guān)系,要么通過對象的行為方法進行互相協(xié)作。

由此可見,我們在進行對象建模的時候,必須優(yōu)先考慮的就是對象與對象之間的關(guān)系模型,關(guān)系模型決定我們進行對象關(guān)聯(lián)的具體形式,選擇合適的編程語言語法進行關(guān)聯(lián)關(guān)系的表達。

將對象之間的協(xié)作和關(guān)聯(lián)關(guān)系作為設(shè)計對象的最重要的考慮因素,可以時刻提醒我們不要將過多的邏輯放在一個對象之中。因為當考慮到對象之間的協(xié)作和關(guān)聯(lián)關(guān)系,我們就可以充分挖掘每一個對象的職責(zé)和語義,從而避免一個對象過于復(fù)雜而變得不可維護。

2.2 框架的本質(zhì)

什么是框架?框架從何而來?為什么要使用框架?這是一系列簡單而又復(fù)雜的問題。簡單,是因為它們本身似乎不應(yīng)該成為問題。框架實實在在存在,并且在開發(fā)中發(fā)揮著重要的作用,我們的日常工作,遵循著框架所規(guī)定的編程模式,在其指導(dǎo)之下,我們能夠編寫更為強大的程序。說其復(fù)雜,是因為框架本身又是如此紛繁復(fù)雜,我們在使用框架的同時,往往會迷失其中。

任何事物都有蘊含在其內(nèi)部的本質(zhì)。無論框架本身有多復(fù)雜,我們所需要探尋的,都是其最為內(nèi)在的東西。框架為什么會產(chǎn)生?我們來看一個最最簡單的例子。

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

          if(str == null || str.length() == 0) {
              // 在這里添加你的邏輯
          }

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

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

代碼清單2-4 StringUtils.java

          // 定義一個類和一個靜態(tài)工具方法來抽象出將被重復(fù)調(diào)用的邏輯
          public abstract class StringUtils {
              // 封裝了一個靜態(tài)方法
              public static boolean isEmpty(String str) {
                  return str == null || str.length() == 0;
              }
          }
          // 引用靜態(tài)方法取代之前的代碼片段
          if(StringUtils.isEmpty(string)) {
              // 在這里添加你的邏輯
          }

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

可讀性

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

可擴展性

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

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

代碼清單2-5 StringUtils.java

              package org.apache.commons.lang;
              public class StringUtils {
              // 這里省略了許多其他的代碼
              public static boolean isEmpty(String str) {
                  return str == null || str.length() == 0;
              }
          }

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

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

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

我們的程序就像一個金字塔形狀。位于最底部的當然是JVM,提供運行Java程序的基礎(chǔ)環(huán)境,包括對整個Java程序的編譯運行。在這個之上的是JDK,JDK是構(gòu)建在JVM之上的基本的對象行為的定義(我們在搭建開發(fā)環(huán)境時所安裝的JDK就是這個)。而再往上,是一個具備層次結(jié)構(gòu)的JAR層,所有被加載到CLASSPATH中的JAR文件都搭建在JDK層次之上,它們之間可能形成互相依賴,但不管怎么說,它們的作用都是提供JDK以外的功能支持。最后,在金字塔尖的,才是我們?nèi)粘>帉懙膽?yīng)用程序,它將依賴于金字塔低端的所有程序。這樣一個結(jié)構(gòu)如圖2-3所示。

圖2-3 Java應(yīng)用的金字塔結(jié)構(gòu)

仔細觀察一下處于中間的JAR層,這個層次的組成結(jié)構(gòu)與其他的層次不同。它是由一塊塊磚頭堆砌而成,上層的磚塊搭建在下層的磚塊之上。如果我們把其中的每一塊磚都比作一個JAR文件,它們之間也就形成了明顯的具備層次的依賴關(guān)系。

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

圖2-4 Eclipse中的CLASSPATH示例

從圖中我們看到,JRE System Library是整個應(yīng)用程序最基本的運行環(huán)境。而無論是Struts2還是Spring,它們都以JAR文件的形式被加載到程序運行所依賴的CLASSPATH中,并為我們的應(yīng)用程序使用。如果我們用更加通俗的話來表述這一現(xiàn)象,則是:

結(jié)論 框架只是一個JAR包而已,其本質(zhì)是對JDK的功能擴展。

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

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

結(jié)論 框架是一組程序的集合,包含了一系列的最佳實踐,作用是解決某個領(lǐng)域的問題。

只有解決問題才是所有框架的共同目標。框架的產(chǎn)生就是為了解決一個又一個在開發(fā)中所遇到的困境。不同的框架,只是為了解決不同領(lǐng)域的問題。所以,對于廣大程序員來說,千萬不要為了學(xué)習(xí)框架而學(xué)習(xí)框架,而是要為了解決問題而學(xué)習(xí)框架,這才是一個程序員的正確學(xué)習(xí)之道。

2.3 最佳實踐

一切程序的編寫,都需要遵循特定的規(guī)范。這里所說的規(guī)范,往往是建立在運行環(huán)境之上的一系列概念和實現(xiàn)方法的基本定義,并被歸納為一個完整的體系。例如,我們使用Java來進行Web開發(fā),所需要遵循的最基本的規(guī)范就是我們所熟悉的Servlet標準、JSP標準,等等。

建立在標準和規(guī)范之上的,是各種針對這些標準和規(guī)范的實現(xiàn)。這些實現(xiàn)構(gòu)成了程序運行的基本環(huán)境。例如,Tomcat有對Servlet標準的實現(xiàn)方式,而Websphere則有不同的實現(xiàn)方式。然而它們在本質(zhì)上都實現(xiàn)了Servlet標準所規(guī)定的接口,從而讓我們的應(yīng)用程序可以透明地使用這些API,而無須關(guān)心真正的Web容器內(nèi)部的實現(xiàn)機理。

我們所編寫的程序,總是建立在一系列的規(guī)范和基本運行環(huán)境之上。面對紛繁復(fù)雜的業(yè)務(wù)需求,不同的程序員可以按照自己的意愿來編寫程序,因此,即使為了表達相同的業(yè)務(wù)功能,不同的程序代碼之間的差異性也是很大的。程序的差異性有時候會給人以創(chuàng)新的靈感,但是更多的時候會造成麻煩。因為差異性越大,維護起來就越麻煩。出于對可維護性和可讀性的要求,我們所希望的程序最好能從宏觀層面上看上去是一致的,使得每一個程序員都能夠讀懂并合理運用,這才是我們的目標。這一目標,我們習(xí)慣上稱之為最佳實踐。

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

所有這些最佳實踐,最終又以一個個JAR包的形式蘊含在框架之中,對我們的應(yīng)用程序提供必要的支持,因此我們有必要在這里探尋一些最最基本的最佳實踐,從更深的層次了解框架存在的意義和框架的設(shè)計初衷。在之后的章節(jié)中,我們會反復(fù)提及這些最佳實踐,因為它們不僅能夠指導(dǎo)我們進行程序開發(fā),它們本身也蘊含在Struts2的內(nèi)部。

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

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

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

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

可讀性、可維護性和可擴展性,就像三腳架的三個支撐腳,缺一不可。任何對程序的重構(gòu),實際上都圍繞著這三個基本原則進行,而它們也同時成為衡量程序?qū)懙煤脡牡淖罨緲藴省4a的不斷重構(gòu)、框架的產(chǎn)生實際上都來自于這三個程序內(nèi)在屬性的驅(qū)動。

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

最佳實踐 簡單是美(Simple is Beauty)。

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

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

最佳實踐 盡可能使用面向?qū)ο蟮挠^點進行編程。

我們可以看到,這個層面的最佳實踐,已經(jīng)從基本準則和指導(dǎo)思想轉(zhuǎn)向了具體的編程層面。雖然面向?qū)ο笞陨硪仓皇且环N編程的指導(dǎo)思想,然而它卻是和程序設(shè)計與實現(xiàn)息息相關(guān)并且對程序編寫影響最大的一條編程準則。

面向?qū)ο筮@個概念本身就是一個非常耐人尋味的問題。要討論面向?qū)ο蟮母拍睢⒃O(shè)計和方法論,恐怕一天一夜都講不完。在本章之初,我們從“對象”這個概念入手,通過對“對象”內(nèi)部結(jié)構(gòu)的分析,試圖向讀者展示面向?qū)ο缶幊讨械囊恍┲匾碚摗Wx者對這些理論不應(yīng)停留在死記硬背的層面,而是要將它們?nèi)谌氲娇蚣艿脑O(shè)計理念中去理解。同時,這些理論也將成為我們判別框架和設(shè)計方案優(yōu)劣的重要標準。

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

之前在分析框架的本質(zhì)時已經(jīng)提到,任何Java程序總是依賴于其運行環(huán)境(JVM層)和支持應(yīng)用程序的JAR層。加入到CLASSPATH中JAR越多,就意味著程序?qū)ν獠凯h(huán)境的依賴度越高,對外部環(huán)境的依賴度越高,就意味著程序本身越難以脫離特定的外部環(huán)境進行單元測試。因此,減少甚至消除依賴,也成為許多框架所追求的目標。

Struts2在這一點上做得尤為成功。Struts2不但實現(xiàn)了Web框架與Web容器之間的解耦合,還在此基礎(chǔ)之上實現(xiàn)了各個編程元素之間的有效溝通。在之后的章節(jié)中,我們會深入探究Struts2在這條最佳實踐上所做出的努力。

2.4 Web開發(fā)的基本模式

到此為止,我們花了大量的篇幅介紹了許許多多與Web開發(fā)完全無關(guān)的東西。無論是面向?qū)ο蟮母拍睢⒖蚣艿谋举|(zhì)內(nèi)容還是我們開發(fā)中應(yīng)當遵循的最佳實踐,它們都是程序員需要培養(yǎng)的內(nèi)在修養(yǎng)。接下來,我們將話題真正轉(zhuǎn)入Web開發(fā),來看看在Web開發(fā)中應(yīng)該遵循什么樣的最佳實踐。

2.4.1 分層開發(fā)模式

之前我們討論了Web開發(fā)中幾條基本的最佳實踐,它們會成為貫穿本書始終的指導(dǎo)思想。明確了指導(dǎo)思想,我們有必要從方法論的角度來探討一下Web開發(fā)的一些基本模式。

從宏觀上來說,Web開發(fā)模式中最最重要的一條是分層開發(fā)模式。分層開發(fā)模式是指,在開發(fā)J2EE程序時,將整個程序根據(jù)功能職責(zé)進行縱向劃分。一個比較典型并為大家所熟知的劃分方法是將整個程序分為:表示層、業(yè)務(wù)層和持久層,如圖2-5所示。

圖2-5 分層開發(fā)模式示意圖

不同的層次,實際上承擔(dān)了不同的功能職責(zé):

表示層(Presentation Layer)—負責(zé)處理與界面交互相關(guān)的功能

業(yè)務(wù)層(Business Layer)—負責(zé)復(fù)雜的業(yè)務(wù)邏輯計算和判斷

持久層(Persistent Layer)—負責(zé)將業(yè)務(wù)邏輯數(shù)據(jù)進行持久化存儲

分層開發(fā)模式是技術(shù)層面的“分而治之”設(shè)計思想的一種體現(xiàn)。而蘊含在其內(nèi)部的驅(qū)動力還是我們反復(fù)強調(diào)的:程序的可讀性和可擴展性。出于可讀性考慮,把不同功能職責(zé)的代碼分開,能夠使程序流程更加清晰明了;出于可擴展性考慮,也只有把相類似的功能歸結(jié)為一個縱向?qū)哟危攀沟梦覀冊谶@個層次之上研究通用的解決方案成為可能。

分層開發(fā)模式,從邏輯上是將開發(fā)職責(zé)分派到不同的對象之上去執(zhí)行的一種設(shè)計思想。回顧我們在面向?qū)ο鬁\談的章節(jié)中所提到的對象協(xié)作關(guān)系,也正是分層開發(fā)模式的理論依據(jù)。

既然是職責(zé)分派,我們就不得不分清什么是職責(zé)、什么樣的對象適合什么樣的職責(zé)。如此一來,有關(guān)分層開發(fā)模式的討論就變成了一個典型的哲學(xué)問題。凡是哲學(xué)問題,都會出現(xiàn)正反兩派。分層開發(fā)模式所涉及的爭論主題主要包括兩個方面:第一,分層開發(fā)到底有無必要?第二,對于一個J2EE程序到底分為多少層進行開發(fā)比較合適?

我們先來探討第一個問題:對一個程序?qū)嵤┓謱娱_發(fā)到底有無必要?分層開發(fā)模式,為程序的可擴展性提供了可能性,但是當問題的作用域變小時,分層開發(fā)模式反而成為一種累贅。有許多程序員會抱怨,一個簡單的邏輯功能,動輒就要十幾個文件配合上百行代碼來完成。此時,我們不禁要問:我們的開發(fā)真的需要分層嗎?分層開發(fā)到底為我們帶來了多少好處呢?針對這一問題,我們不妨來看看Struts2的一個官方的FAQ,如圖2-6所示。

圖2-6 Struts的FAQ

非常明顯,當問題的作用域發(fā)生變化時,解決問題的方法也要相應(yīng)做出改變。所以,分層開發(fā)模式,對于大型企業(yè)應(yīng)用或者產(chǎn)品級的應(yīng)用程序開發(fā)是有著重要意義的;然而當一個應(yīng)用程序足夠小,并且需求的變更處于可控的范圍之內(nèi)時,我們對于分層開發(fā)模式的選擇應(yīng)該謹慎。這就是所謂的“殺雞焉用牛刀”。

我們再來看看第二個問題:對于一個J2EE程序,到底要分多少層進行開發(fā)比較合適?這是一個與整個應(yīng)用程序構(gòu)架相關(guān)的話題。有許多程序員贊同分層開發(fā)模式,不過他們都希望將層次分得盡量簡單,崇尚“簡單是美”的原則。對于這一問題,實際上也沒有絕對正確的答案。因為一切脫離了業(yè)務(wù)實際的架構(gòu)設(shè)計都是虛幻的。我們只能在實踐中不斷總結(jié),并將前人的許多經(jīng)驗作為我們進行開發(fā)層次劃分的重要依據(jù),選擇適合于實際業(yè)務(wù)需求的開發(fā)層次,才是程序開發(fā)的最佳實踐。

這些有關(guān)分層開發(fā)的哲學(xué)問題的討論,每個程序員都有自己的見解。然而從框架的角度,我們也能看出一些框架的設(shè)計者對于某個開發(fā)層次的理解,因為我們最最熟悉的這些著名的框架,實際上就是為了應(yīng)對各個開發(fā)層次的編程問題而設(shè)計的解決方案。比如說:

Struts2是表示層的框架;Spring是業(yè)務(wù)層的框架;Hibernate是持久層的框架。

在本書中,我們所有討論的重點實際上是圍繞著表示層的解決方案 — Struts2進行的。筆者花了那么多筆墨,才把Struts2這位主人公引出來的目的,是希望讀者能夠站在全局的高度來審視Struts2,也只有這樣,才能夠真正學(xué)好每一個開源框架。

2.4.2 MVC模式

在分層開發(fā)模式的前提下,每一個層次都可以單獨研究,并尋找合適的解決方案和最佳實踐。對于表示層,有一種稱之為MVC模式的最佳實踐被廣泛使用,并在此基礎(chǔ)上創(chuàng)建了許多基于這種模式的開發(fā)框架。

MVC模式實際上是眾多經(jīng)典的Java開發(fā)模式中的一種。它的基本原理是通過元素分解,來處理基于“請求-響應(yīng)”模式的程序中的各種問題。

M (Model)—數(shù)據(jù)模型

V (View)—視圖展現(xiàn)

C(Control)—控制器

任何一個B/S應(yīng)用,其本質(zhì)實際上是一個“請求-響應(yīng)”的處理過程的集合體。那么MVC模式是如何被提煉出來并成為一個模式的呢? 我們來模擬一個“請求-響應(yīng)”的過程,如圖2-7所示。

圖2-7 請求-響應(yīng)模式

在整個請求-響應(yīng)過程中,有哪些元素是必不可少的呢?

數(shù)據(jù)模型

在圖中,就是順著箭頭方向進行傳輸?shù)臄?shù)據(jù),它們是程序的核心載體,也是貫穿程序運行的核心內(nèi)容。

對外交互

在圖中,對外交互表現(xiàn)為一個“頭”和一個“尾”。“頭”指的是請求發(fā)起的地方,沒有請求,一切之后的所有內(nèi)容都無從談起。“尾”指的是邏輯執(zhí)行完成后,對外展現(xiàn)出來的執(zhí)行結(jié)果。在傳統(tǒng)意義上,我們利用HTML擴展的技術(shù)(如JSP等)來實現(xiàn)對外交互,在展現(xiàn)結(jié)果時,我們還需要完成一定的展現(xiàn)邏輯,比如錯誤展示、分支判斷,等等。

程序的執(zhí)行和控制

實際上它不僅是接受請求數(shù)據(jù)的場所,也是處理請求的場所。在請求處理完畢之后,它還要負責(zé)響應(yīng)跳轉(zhuǎn)。這個部分可能會存在著不同的表現(xiàn)形式。以前,我們用JSP和Servlet,后來用Struts1或者Struts2的Action。而這一變化,實際上包含了我們不斷對程序進行重構(gòu)的過程。

上面這3大元素,在不同的年代被賦予了不同的表現(xiàn)形式。例如,在很久以前,我們使用Servlet或者JSP來編寫程序跳轉(zhuǎn)的控制過程,有了Struts1.X后,我們使用框架所定義的Action類來處理。這些不同的表現(xiàn)形式有的受到時代的束縛,表現(xiàn)形式非常落后,有的甚至已經(jīng)不再使用。但是我們忽略這些外在的表現(xiàn)形式就可以發(fā)現(xiàn),這不就是我們已經(jīng)熟悉的MVC嗎?

數(shù)據(jù)模型—Model

對外交互—View

程序的執(zhí)行和控制—Control

MVC的概念就這么簡單,這些概念其實早已深入我們的內(nèi)心,而我們所缺乏的是將其本質(zhì)挖掘出來的能力。我們來看看如圖2-8所示的這幅流行了很多年的講述MVC模型的圖。

圖2-8 MVC模型圖

在這幅圖中,MVC三個框框各司其職,結(jié)構(gòu)清晰明朗。這也成為我們進行編程開發(fā)的最強有力的理論武器,我們需要做的,只是為這些框框賦予不同的表現(xiàn)形式。實際上,框架就是這么干的!而框架的高明之處,僅僅在于它不僅賦予這些元素正確而恰當?shù)谋憩F(xiàn)形式,同時解決了當元素運行起來時所碰到的各種問題。因此,我們始終應(yīng)該做到:程序時時有,概念心中留。只要MVC的理念在你心中,無論程序怎么變,都能做到萬變不離其宗。

2.5 表示層的困惑

當表示層有了MVC模式,程序開發(fā)就會變得有章可循。至少,我們不會像無頭蒼蠅一樣無從入手。MVC模式很直觀地規(guī)定了表示層的各種元素,只要能夠通過恰當?shù)某绦虮憩F(xiàn)形式來實現(xiàn)這些元素,我們實際上已經(jīng)在履行最佳實踐了。

至此,我們不妨返璞歸真,忘記所謂的框架,用最簡單的方式來實現(xiàn)一個簡單的MVC雛形。在這個過程中,我們不妨回到框架的本質(zhì)問題上,思考一下究竟一個框架為表示層解決了什么樣的編程難題,難道框架只是實現(xiàn)MVC這三大元素那么簡單而已?

我們選擇Registration(注冊)作為業(yè)務(wù)場景。首先,我們需要一個JSP頁面來呈現(xiàn)用戶注冊的各個字段、一個User類來表示用戶實體以及一個RegistrationServlet類來處理注冊請求。相關(guān)實現(xiàn)源碼如代碼清單2-6、代碼清單2-7和代碼清單2-8所示。

代碼清單2-6 registration.jsp

          <form method="post" action="/struts2_example/registration">
            user name: <input type="text" name="user.name" value="downpour" />
            birthday: <input type="text" name="user.birthday" value="1982-04-15" />
            <input type="submit" value="submit" />
          </form>

代碼清單2-7 User.java

          public class User {
              private String name;
              private Date birthday;
              public User() {
              }
              // 此處省略setter與getter方法
          }

代碼清單2-8 RegistrationServlet.java

          public class RegistrationServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest req, HttpServletResponse
          resp) throws ServletException, IOException {
                  // 從request獲取參數(shù)
                  String name = req.getParameter("name");
                  String birthdayString = req.getParameter("birthday");
                  // 做必要的類型轉(zhuǎn)化
                  Date birthday = null;
                  try {
                      birthday = new SmpleDateFormat("yyyy-MM-dd").
          parse(birthdayString);
                  } catch (ParseException e) {
                  e.printStackTrace();
                  }
                  // 初始化User類,并設(shè)置字段到user對象中去
                  User user = new User();
                  user.setName(name);
                  user.setBirthday(birthday);
                  // 調(diào)用業(yè)務(wù)邏輯代碼完成注冊
                  UserService userService = new UserService();
                  userService.register(user);
                  req.getRequestDispatcher("/success.jsp").forward(req, resp);
              }
          }

除了上述這3段源代碼外,我們還需要建立起JSP頁面中的form請求與Servlet類的響應(yīng)之間的關(guān)系。這一關(guān)系,是在web.xml中維護的,如代碼清單2-9所示。

代碼清單2-9 web.xml

          <servlet>
            <servlet-name>Register</servlet-name>
            <servlet-class>example.RegistrationServlet</servlet-class>
          </servlet>
          <servlet-mapping>
            <servlet-name>Register</servlet-name>
            <url-pattern>/struts2_example/registration</url-pattern>
          </servlet-mapping>

我們來看看上面的這4段代碼是如何構(gòu)成MVC的雛形的。

Model(數(shù)據(jù)模型)—User.java

View(對外交互)—registration.jsp

Control(程序執(zhí)行和控制)—RegistrationServlet.java

URL Mapping(請求轉(zhuǎn)化)—web.xml

我們可以看到MVC的實現(xiàn)似乎并不復(fù)雜。在不借助額外的框架幫助的前提下,只要基本知曉JSP和Servlet標準(它們是使用Java進行Web開發(fā)的規(guī)范和標準),任何程序員都可以像模像樣地實現(xiàn)MVC模式,因為從原理上講,MVC只是一個概念,我們只需要把這個概念中的各個元素賦予相應(yīng)的程序?qū)崿F(xiàn)即可。

不過程序終究是一個動態(tài)的執(zhí)行過程。一旦程序開始運行,上面的這些程序?qū)崿F(xiàn)就會開始遭遇種種困境。這些困境主要來源于兩個方面:其一,出于程序自身的可讀性和可維護性考慮,需要通過重構(gòu)來解決程序的復(fù)雜性困境。其二,出于業(yè)務(wù)擴展的需求,需要通過框架級別的功能增強來解決可擴展性困境。

問題1 當瀏覽器發(fā)送一個Http請求,Web容器是如何接收這個請求并指定相應(yīng)的Java類來執(zhí)行業(yè)務(wù)邏輯并返回處理結(jié)果的?

這個問題是使用Java進行Web開發(fā)的核心問題之一,我們將這個問題簡稱為URL Mapping問題。這個問題的本質(zhì)實際上來源于Http協(xié)議與Java程序之間的匹配和交互。Web開發(fā)經(jīng)過了多年的發(fā)展,這一核心的哲學(xué)問題也經(jīng)歷了多次重大變革,有的崇尚由繁至簡,有的則從形式多樣化入手。

在上面的例子中,我們可以看到使用web.xml來表達URL Mapping關(guān)系遇到的困境:當系統(tǒng)變大,這種配置上的重復(fù)操作會讓web.xml變得越來越大而難以維護。不僅如此,web.xml的配置也無法為URL Mapping建立起合適的規(guī)則引擎。

由此,解決URL Mapping問題的核心在于建立一套由Http協(xié)議中的URL表達式到Java世界中類對象的規(guī)則匹配引擎。額外的,這種規(guī)則匹配最好比較靈活而簡單又不失必要的可維護性。

問題2 Web應(yīng)用是典型的“請求-響應(yīng)”模式的應(yīng)用,數(shù)據(jù)是如何順利流轉(zhuǎn)于瀏覽器和Java世界之間的?面對Http協(xié)議與Java世界數(shù)據(jù)形式的不匹配性,我們?nèi)绾文軌蛟诹鬓D(zhuǎn)時做到數(shù)據(jù)類型的自動轉(zhuǎn)化?

這個問題伴隨著問題1而來,數(shù)據(jù)請求與數(shù)據(jù)返回相當于是基于“請求-響應(yīng)”模式的Web程序的輸入和輸出。數(shù)據(jù)的本質(zhì)是存儲于其中的信息,只不過數(shù)據(jù)在不同的地方有不同的表現(xiàn)形式。例如,在瀏覽器中,數(shù)據(jù)總是以字符串形式展現(xiàn)出來,表現(xiàn)出“弱類型”的特征;在Java世界,數(shù)據(jù)則體現(xiàn)為一個個結(jié)構(gòu)化的Java對象,表現(xiàn)出“強類型”的特征。于是,就需要有一個工具能夠幫助我們解決在數(shù)據(jù)流轉(zhuǎn)時的數(shù)據(jù)形式的相互轉(zhuǎn)化。

在上面的例子中,我們可以看到RegistrationServlet中,我們編寫了額外的代碼,把頁面上傳遞過來的日期值轉(zhuǎn)化為Java中的Date對象。在參數(shù)的數(shù)量和Java對象越來越復(fù)雜的情況下,這種額外的代碼就會變成一種災(zāi)難,甚至成為我們開發(fā)的主要瓶頸之一。

解決數(shù)據(jù)流轉(zhuǎn)問題的方案是使用表達式引擎。將表達式引擎插入到程序邏輯執(zhí)行之前,我們就能從復(fù)雜的對象轉(zhuǎn)化中解放出來,從而進一步簡化開發(fā)流程。

問題3 Web容器是一個典型的多線程環(huán)境,針對每個Http請求,Web容器的線程池會分配一個特定的線程進行處理。那么如何保證在多線程環(huán)境下,處理請求的Java類是線程安全的對象?如何保證數(shù)據(jù)的流轉(zhuǎn)和訪問都是線程安全的?

這個問題與問題1一樣,也是Web開發(fā)中的核心問題之一,因為它涉及Web開發(fā)中最為底層的處理機制問題。在上面的例子中,我們使用的是基于Servlet標準的方式進行編程,擴展Servlet用于處理Http請求。然而恰恰就是這種編程模型,是一種非線程安全的編程模型,因為Servlet對象是一個非線程安全的對象。也就是說,如果我們在doPost方法中訪問RegistrationServlet中所定義的局部變量,就會產(chǎn)生線程安全問題(第4章會重點介紹線程安全問題產(chǎn)生的來龍去脈)。

傳統(tǒng)的表示層框架對于這個問題的處理方式是采用規(guī)避問題的方式。既然Servlet對象不是一個線程安全的對象,那么我們就干脆禁止在Servlet對象的方法中訪問Servlet對象的內(nèi)部變量。這種鴕鳥算法固然是一種有效的方案,但它卻不是一種合理的方案。最致命的一點是,它是一種非語法檢查級別的禁止,因此也就無法從根本上杜絕程序員犯這樣的錯誤。

另外一種解決方案就是在整個請求周期中引入ThreadLocal模式,通過ThreadLocal模式的使用,將整個過程的對象訪問都線程安全化,徹底解決多線程環(huán)境下的數(shù)據(jù)訪問問題(有關(guān)ThreadLocal模式的方方面面,我們在后續(xù)章節(jié)中會詳細介紹)。ThreadLocal模式的引入對于Web層框架的影響是深遠并且顛覆性的,因為它為框架擺脫Web容器的依賴鋪平了道路,意味著我們可以通過合理的設(shè)計,在脫離Servlet等Web容器元素的環(huán)境中進行編程。

問題4 Controller層作為MVC的核心控制器,如何能夠在最大程度上支持功能點上的擴展?

問題4來源于我們對程序本身的自然屬性(可讀性和可擴展性)的需求。這一內(nèi)在需求實際上也驅(qū)動著我們著手在整個MVC的構(gòu)架級別設(shè)計更為成熟有效的自擴展方案。

從一個更加宏觀的角度來幫助我們理解這個問題,我們來舉一個制藥工廠生產(chǎn)藥品的例子。一個工廠在進行批量生產(chǎn)時,總是會引入“生產(chǎn)線”的概念。生產(chǎn)線能夠把整個制藥過程劃分成若干道工序,當原材料經(jīng)過每一道工序,最終就會成為一個可出廠銷售的藥品。某一天,由于市場推廣的原因,需要改變藥品的包裝,那么我們對這條生產(chǎn)線的要求就是它能夠改變“包裝”這道工序的流程,更改成新的包裝。

在上面的例子中,我們可以看到并沒有一個“生產(chǎn)線”的概念。這種情況下,我們?nèi)蘸髮τ谶壿嫻δ艿臄U展就變得困難重重。雖然我們發(fā)現(xiàn),RegistrationServlet或許和其他所有的Servlet有著非常類似的執(zhí)行步驟:接收參數(shù)、進行類型轉(zhuǎn)換、調(diào)用業(yè)務(wù)邏輯接口執(zhí)行邏輯、返回處理結(jié)果。然而我們卻缺乏一條可以任意配置調(diào)度的生產(chǎn)線將這個過程規(guī)范起來。

解決這個問題從直觀上來講似乎很容易:沒有生產(chǎn)線,我們建一條生產(chǎn)線就行了。而事實上,“造輪子”實在是一件費時費力的事情,因為我們要考慮的方面實在太多。這時我們就不得不借鑒許多前輩的經(jīng)驗了,尋找某些事件定義的框架,遵循框架的定義規(guī)范來進行編程將是我們解決這個問題的主要途徑。

問題5 View層的表現(xiàn)形式總是多種多樣的,隨著Web開發(fā)技術(shù)的不斷發(fā)展,MVC如何在框架級別提供一種完全透明的方式來應(yīng)對不同的視圖表現(xiàn)形式?

這一問題是基于View(視圖)技術(shù)的不斷發(fā)展,造成傳統(tǒng)的基于HTML的視圖已經(jīng)不能滿足所有的需求而提出的。當今,越來越多新的視圖技術(shù)被用于Web開發(fā)中,例如,模板技術(shù)、JSON數(shù)據(jù)流、Stream數(shù)據(jù)流、Flash展現(xiàn)等等。

在上面的例子中,我們可以看到負責(zé)視圖層跳轉(zhuǎn)的RegistrationServlet是通過硬編碼方式完成程序執(zhí)行跳轉(zhuǎn)的。這種方式不但無法支持多種新的視圖技術(shù),同時也無法使我們從復(fù)雜的視圖跳轉(zhuǎn)的硬編碼中釋放出來。

解決這個問題的最有效途徑是把不同的視圖技術(shù)進行分類,針對不同的分類封裝不同的視圖跳轉(zhuǎn)邏輯,而最重要的一步是將這兩者與之前我們所提到的生產(chǎn)線有機結(jié)合起來。

問題6 MVC模式雖然很直觀地為我們規(guī)定了表示層的各種元素,但是如何通過某種機制把這些元素有機整合在一起,從而成為一個整體呢?

這個問題非常宏觀,卻是我們不得不去面對的一個問題。MVC雖然在概念上被規(guī)定下來,在實現(xiàn)上卻需要一個完整的機制來把這些元素都容納在一起。通常情況下,我們往往把這種機制稱之為配置元素。配置元素是構(gòu)成程序的重要組成部分,它把各種形式的程序通過某種配置規(guī)則聯(lián)系在一起。之前我們提到的URL Mapping實際上也屬于配置規(guī)則的一種,視圖的跳轉(zhuǎn)也是配置規(guī)則的一種。只有當這種配置規(guī)則被建立起來,MVC模式才能真正運作起來。

這一系列配置元素在框架內(nèi)部往往被定義成統(tǒng)一的可以被框架識別的數(shù)據(jù)結(jié)構(gòu)并在系統(tǒng)初始化的時候進行緩存。而這些被緩存了的對象,也成為主程序的控制流在MVC框架中各個元素之間進行流轉(zhuǎn)的依據(jù)。

如果從元素的表現(xiàn)形式上來看配置元素和控制流的關(guān)系,我們實際上可以看到整合過程的兩個層面:數(shù)據(jù)結(jié)構(gòu)和流程控制。所謂的框架,我們也只是在這兩個層面上做文章,一方面規(guī)定好這些配置元素的定義,另一方面指定程序運轉(zhuǎn)的流程,從而控制和整合散落在各處的表示層元素。

2.6 如何學(xué)習(xí)開源框架

正確的學(xué)習(xí)方法不僅能夠事半功倍,也能夠使我們更加接近真理。在了解了框架的本質(zhì)和Web開發(fā)模式之后,我們來討論一下學(xué)習(xí)開源框架的基本方法。

在這里為大家總結(jié)了一些正確的學(xué)習(xí)方法和最佳實踐,這些不僅是筆者多年開發(fā)中的心得體會,也汲取了網(wǎng)絡(luò)上的大家之言,希望對初學(xué)者或者正在為學(xué)習(xí)開源框架犯愁的朋友帶來一些啟示。這些學(xué)習(xí)方法,不僅適用于Struts2,同樣適用于許多其他的開源框架。

最佳實踐 閱讀、仔細閱讀、反復(fù)閱讀每個開源框架自帶的Reference。

這是學(xué)習(xí)框架最為重要,也是最開始最需要做的事情。不幸的是,事實上,絕大多數(shù)程序員對此并不在意,并且總是以種種理由作為借口不仔細閱讀Reference。

程序員的常見借口之一:英語水平跟不上,英文文檔閱讀起來太吃力。針對這樣的借口,我們需要指出,閱讀英文文檔是每個程序員必須具備的基本素質(zhì)之一,這就和調(diào)試程序需要耐心一樣,對一個程序員來說非常重要。當然,閱讀英文文檔這一基本素質(zhì)是一點一滴積累培養(yǎng)起來的,對于那些閱讀起來實在覺得吃力的朋友,筆者的建議是結(jié)合中文的翻譯版本一起看。國內(nèi)有許多開源組織,例如滿江紅的開源支持者已經(jīng)為大家精心做了許多很有價值的翻譯,例如Spring、Hibernate等都有對應(yīng)的中文翻譯文檔。但是大家必須注意,看中文文檔,必須和英文文檔對照,因為沒有人可以確保翻譯能夠百分之百正確,語義的不匹配會給你帶來極大的誤導(dǎo),通過對照,才能夠?qū)⒄`解降到最低。

程序員的常見借口之二:Reference太長,抓不住重點。在這里,筆者給出的建議是:耐心,耐心,還是耐心!從Reference的質(zhì)量而言,其實大多數(shù)開源框架的Reference都是非常優(yōu)秀的,基本包含了框架的方方面面。尤其是Struts2,由于歷史原因,Struts2的Reference基本上都是一個一個的專題Wiki文章拼起來的文檔,每篇文章都有一個固定的主題,不僅包含原理解析、注意事項,有的還包含源碼解析和示例講解。閱讀Reference可能會非常枯燥,但是從價值的角度看,對Reference的閱讀往往是對大家?guī)椭畲蟮摹R虼耍P者對閱讀Reference的建議是,多看幾遍。第一遍,你可以采取瀏覽(scan)的方式,目的是了解框架的整體架構(gòu)的大致功能。第二遍,挑重點章節(jié)仔細閱讀,并且輔以一定的代碼實踐,目的是徹底掌握某個分支領(lǐng)域的知識。第三遍,帶著問題閱讀,在文檔中尋找答案。

筆者之所以強烈推薦大家仔細閱讀開源框架自帶的Reference,主要基于以下的兩個原因:

權(quán)威性

這些自帶的Reference多數(shù)出自這些開源框架的作者或者開發(fā)人員之手。還有誰能夠比他們自己更了解他們自己編寫的產(chǎn)品呢?自己寫的程序,到底有哪些優(yōu)點,如何使用,自己肯定是最最清楚的,所以要說到權(quán)威性,不可能有任何文檔比自帶的Reference更加權(quán)威。

正確性

自帶的Reference幾乎很少犯錯,所以不會給你帶來什么誤導(dǎo)信息。不僅如此,許多Reference已經(jīng)為你總結(jié)了框架使用過程中的許多最佳實踐。所以我們沒有理由不直接通過這些Reference來獲得第一手的資料。

最佳實踐 精讀網(wǎng)絡(luò)教程。

對于很多初學(xué)者來說,他們對看Reference這種學(xué)習(xí)方式的接受程度很低。相反,他們會去轉(zhuǎn)而學(xué)習(xí)一些網(wǎng)絡(luò)教程。一般而言,這些學(xué)習(xí)材料的實際價值要比Reference低很多。主要原因在于,作者在編寫這些教程時,多數(shù)都會加入他們自己的學(xué)習(xí)思路,而忽略了框架本身所期望達到的程序開發(fā)最佳實踐,甚至?xí)o很多讀者以:“程序就是這么寫的”的誤導(dǎo)。所以,對于網(wǎng)絡(luò)上的絕大多數(shù)網(wǎng)絡(luò)教程,需要讀者有足夠的甄別能力,否則很容易被帶入歧途。

網(wǎng)絡(luò)上還有很多原版教程,例如《XXX in Action》系列。《XXX in Action》系列的書籍在市場上深受好評。然而,這些系列的書籍有些內(nèi)容也帶有作者個人的感情色彩。當然,每個作者在編寫書籍或撰寫教程的過程中都會夾帶自己的感情色彩,這本不是什么壞事,不過既然我們已經(jīng)有了Reference作為閱讀的主體了,對這類書籍,我們需要采取的態(tài)度是“精讀”。

很多網(wǎng)絡(luò)教程,尤其是中文的網(wǎng)絡(luò)教程,基本上都是網(wǎng)友的經(jīng)驗之談,也有寫成系列文章的。對于網(wǎng)絡(luò)教程,筆者的建議是:帶著問題去讀,去搜索你的答案,而不要當作核心文檔來閱讀。在找到答案之后,也需要通過實踐來反復(fù)驗證,因為許多解決方案可能只是臨時的,并不是程序開發(fā)中的最佳實踐。

最佳實踐 搭建環(huán)境運行每個開源框架自帶的sample項目。

每個開源框架基本上都會自帶有sample項目。以Struts2為例,在Struts2的分發(fā)包的apps目錄下就有多個sample項目,如圖2-9所示。

圖2-9 Struts2自帶的sample項目

Struts2是一個典型的Web層框架,所以所有Struts2的sample項目都以war包的形式給出,大家可以將這些war包的任何一個復(fù)制到你的Web容器的運行目錄下,啟動Web容器就可以訪問這些sample項目。

千萬不要小看這些sample項目,我們可以從這些項目中獲取許多重要的知識和信息。有些知識恐怕連Reference都不曾提及。這些原生態(tài)的東西,使得我們完全無須舍近求遠地到網(wǎng)絡(luò)上去到處尋找例子,只要學(xué)習(xí)這些例子,就足以掌握開源框架的種種特性了。

我們可以就其中的三個sample項目進行舉例分析。

struts2-blank-2.2.1.war

一般而言,名為xx-blank-xxx.war的sample項目是一個開源框架的一個最小可運行范例。所以,如果大家仔細學(xué)習(xí)這個war包中的內(nèi)容,至少可以發(fā)現(xiàn)組成一個Struts2程序的最小元素到底有哪些。在其中的WEB-INF/lib目錄下,我們能夠找到Struts2程序運行所需要依賴的JAR包的最小集合(如圖2-10所示),我們還能從中學(xué)習(xí)Struts2的各種基礎(chǔ)配置的編寫等。

圖2-10 Struts2所依賴的基本JAR包的最小集合

struts2-portlet-2.2.1.war

這個sample項目告訴我們在Portal環(huán)境下的Struts2的應(yīng)用應(yīng)該如何編寫。通過與struts2-blank-2.2.1.war這個項目的比較,大家可以發(fā)現(xiàn),Struts2在應(yīng)對不同的應(yīng)用服務(wù)器環(huán)境方面的不同。

struts2-showcase-2.2.1.war

這個sample項目是Struts2特性的一個大雜燴,包含了絕大多數(shù)的Struts2的特性示例。這個sample項目對于大家閱讀Reference是非常有幫助的。比如說,大家在閱讀文檔時看到了“文件上傳”的章節(jié),那么大家就可以參考這個項目中的upload子目錄中的相關(guān)的類和配置。這相當于一邊看文檔,一邊已經(jīng)有一個現(xiàn)成的可以運行的例子輔助你進行學(xué)習(xí)。所以,這個項目與Reference的搭配是相得益彰、互為補充的,可以作為大家學(xué)習(xí)Struts的最佳資源。

最佳實踐 自己寫一個sample項目親身體驗。

這一點其實不用多說,大家也應(yīng)該明白。不過筆者還是見過不少程序員,眼高手低,整天吹噓這個框架的優(yōu)點,那個框架的優(yōu)勢,但如果讓他自己動手用這些框架寫一段程序,又變得手足無措。

實踐是檢驗真理的唯一標準。只有自己親自動手去實踐,才能說明你真正掌握了某種技術(shù),理解了某個框架的特性。在編寫自己的sample項目時,大家可以針對不同的特性,人為設(shè)置好業(yè)務(wù)場景(例如,使用“登錄”作為一個基本的業(yè)務(wù)場景),在實踐中不斷地重構(gòu)你的代碼,從而領(lǐng)悟框架開發(fā)中的最佳實踐,提升自己的開發(fā)水平。

最佳實踐 帶著問題調(diào)試(Debug)開源框架的源碼。

如果大家對某個開源框架的使用已經(jīng)比較熟練,對其內(nèi)部的原理也基本掌握,或許你就會對其中的某些設(shè)計原理和設(shè)計思想產(chǎn)生興趣。這個時候,通過開源框架的源碼來尋找問題的答案不失為一個很好的進一步學(xué)習(xí)的途徑。

在學(xué)習(xí)開源框架的源碼時,筆者的建議是當程序運行在Debug模式的狀態(tài)下,對源碼進行調(diào)試,在Debug的過程中,查看在開源框架的內(nèi)部到底運行了哪些類,它們的執(zhí)行順序是怎樣的以及這些類中臨時變量的執(zhí)行狀態(tài)。筆者堅決反對逐個package地去閱讀源碼,這毫無意義。因為程序本身是一個整體,程序之所以成為程序,其本質(zhì)在于它是動態(tài)的、運行的。如果我們逐一去閱讀源碼,就相當于把一個完整的整體進行肢體分解,那么我們將永遠無法看到一個完整的動態(tài)執(zhí)行過程。學(xué)習(xí)源碼,最重要的一點在于抓住一個程序在運行過程中某一時刻某個關(guān)鍵類的運行狀態(tài)和最終狀態(tài),而這些都能通過調(diào)試源碼來實現(xiàn),這才是閱讀源碼的最佳實踐。

2.7 小結(jié)

本章討論的話題是非常重要的,因為任何細節(jié)都無法脫離基本概念而存在。如果我們要探尋Struts2的細節(jié),就必須了解Struts2作為一個框架存在的基本意義。本章從面向?qū)ο蟮幕靖拍钫勂穑接懥丝蚣艿谋举|(zhì),揭示了Web開發(fā)過程與框架之間的依存關(guān)系、Web開發(fā)中的一些最佳實踐,并由此提出Web開發(fā)中的一些核心問題。最后,筆者還給出了正確學(xué)習(xí)Struts2的方法供讀者參考。這些學(xué)習(xí)方法是非常有價值的,建議有經(jīng)驗的程序員也看一看。

讀完本章,大家不妨帶著本章提出的這些核心問題,到本書其他的章節(jié)去尋找答案。等到所有的問題都迎刃而解之時,或許大家對于框架和Web開發(fā)的理解也將更上一層樓。

回顧本章的所有內(nèi)容,大家對下面這些問題是否有了一個大致的答案呢?

對象有哪三種構(gòu)成模式?

對象有哪些關(guān)系模型?

什么是框架?框架存在的根本目的是什么?

在整個Web開發(fā)的過程中,我們應(yīng)該牢記哪些最佳實踐?

什么是MVC模式?MVC模式對于Web開發(fā)的主要作用是什么?

在Web開發(fā)中,我們將遇到哪些主要的困境?

Struts2運行所依賴的最少的JAR文件資源的組合有哪些構(gòu)成?

如何正確學(xué)習(xí)一個開源框架?

主站蜘蛛池模板: 山阳县| 洪江市| 竹溪县| 石门县| 河北区| 邳州市| 高安市| 新安县| 纳雍县| 琼结县| 大港区| 越西县| 临清市| 醴陵市| 三门县| 牙克石市| 库车县| 石阡县| 丹阳市| 仪征市| 宜宾县| 肇东市| 二连浩特市| 长泰县| 隆尧县| 即墨市| 永寿县| 苏州市| 凤翔县| 鹤峰县| 松阳县| 建阳市| 镇沅| 涿鹿县| 淳化县| 南宫市| 漠河县| 大英县| 平凉市| 乐清市| 弋阳县|