轉(zhuǎn)自: CCIENET
自從J2EE出現(xiàn)以來(lái),就大大簡(jiǎn)化了在Java下的企業(yè)級(jí)開(kāi)發(fā)。但是隨著J2EE越來(lái)越普遍 地被應(yīng)用到各個(gè)領(lǐng)域中,開(kāi)發(fā)者們漸漸意識(shí)到需要一種方法來(lái)標(biāo)準(zhǔn)化應(yīng)用程序的開(kāi)發(fā)過(guò)程,他們采用的方法是標(biāo)準(zhǔn)化應(yīng)用程序的結(jié)構(gòu)層。在結(jié)構(gòu)層通常封裝了一些獨(dú) 立于業(yè)務(wù)邏輯的復(fù)雜技術(shù),以便在業(yè)務(wù)邏輯和底層的架構(gòu)之間建立起弱連接。無(wú)可否認(rèn),J2EE是一個(gè)很成功的技術(shù),它為一些基本的任務(wù)提供了一致的標(biāo)準(zhǔn),例 如數(shù)據(jù)庫(kù)連接、分布式應(yīng)用程序等。但是使用J2EE并不能保證開(kāi)發(fā)人員開(kāi)發(fā)出成功的應(yīng)用程序。有些人認(rèn)為J2EE本身就是一種框架技術(shù),但是這種認(rèn)識(shí)是不 正確的,我們應(yīng)該意識(shí)到J2EE并沒(méi)有提供一個(gè)能夠幫助開(kāi)發(fā)人員開(kāi)發(fā)出高質(zhì)量應(yīng)用程序的框架,因此很多有經(jīng)驗(yàn)的開(kāi)發(fā)人員通過(guò)利用設(shè)計(jì)模式來(lái)彌補(bǔ)這一缺陷。
在開(kāi)發(fā)人員的圈子中,大家通過(guò)相互交流在開(kāi)發(fā)過(guò)程中所遇到的問(wèn)題以及解決方法來(lái)豐 富整個(gè)圈子的經(jīng)驗(yàn)。而設(shè)計(jì)模式就是在這樣的情況下產(chǎn)生的。一個(gè)設(shè)計(jì)模式必然是針對(duì)某個(gè)特定的問(wèn)題的,這個(gè)問(wèn)題的解決方案以及這樣解決問(wèn)題產(chǎn)生的后果。在本 文中我將討論復(fù)合模式的特點(diǎn)和它的應(yīng)用。
在介紹復(fù)合模式前,我們需要定義一下什么是復(fù)合對(duì)象(Composite Object)。復(fù)合對(duì)象是包含了其它對(duì)象的對(duì)象。例如,一幅圖由一些基本的對(duì)象組成,例如線、圓、矩形和文本等,因此圖就是復(fù)合對(duì)象。因?yàn)樵贘ava 中,開(kāi)發(fā)人員操作基本對(duì)象的方式和操作復(fù)合對(duì)象的方式常常相同,因此需要利用到復(fù)合模式。例如,線或文本等基本圖形對(duì)象都需要支持繪制、移動(dòng)或縮放等功 能;而圖這種復(fù)合對(duì)象也需要支持相同的功能。在理想的情況下,我們希望對(duì)復(fù)合對(duì)象和基本對(duì)象以完全相同的方式完成這些操作,否則實(shí)現(xiàn)的代碼將會(huì)產(chǎn)生不必要 的復(fù)雜性,并且不易于維護(hù)和擴(kuò)展。
那么什么是復(fù)合模式呢?將對(duì)象組織到樹(shù)結(jié)構(gòu)中以表達(dá)部分整體的層次關(guān)系就實(shí)現(xiàn)了復(fù)合模式,它使程序能夠以相同的方式對(duì)待基本對(duì)象和復(fù)合對(duì)象。
在程序中實(shí)現(xiàn)復(fù)合模式并不難。復(fù)合類繼承一個(gè)代表基本類型的基類就可以了 。圖1顯示了一個(gè)表現(xiàn)復(fù)合模式思想的類圖。

在圖1中,Component是代表基本類型的基類(也可以是一個(gè)接口); Composite是復(fù)合類。例如Component代表的是基本圖形元素的基類,而Composite代表的是圖;Operation1()和 Operation2()方法分別是移動(dòng)和縮放操作。圖1中的Leaf類代表的是點(diǎn)、線或者圓等基本圖形元素。
針對(duì)Component類中的每個(gè)方法,Composite類都有相同名稱的方法 與之對(duì)應(yīng)。Composite類保存了一個(gè)基本對(duì)象的集合。通常Composite類中的方法在實(shí)現(xiàn)時(shí)都會(huì)將集合中的對(duì)象遍歷一次,然后調(diào)用每個(gè)對(duì)象中相 應(yīng)的方法。例如圖對(duì)象Drawing的draw()方法可能是這樣實(shí)現(xiàn)的:
public ? void ?draw()?{
??? // ?I遍歷所有的對(duì)象
??? for ( int ?i = 0 ;?i? < ?getComponentCount();? ++ i)?{
?????? // ?獲得對(duì)對(duì)象的應(yīng)用,調(diào)用它的draw方法
??????Component?component? = ?getComponent(i);
??????component.draw();???
???}
由于Composite類繼承了Component類,因此你可以將一個(gè)Composite對(duì)象傳遞給需要Component對(duì)象作為參數(shù)的方法。例如:
public ? void ?repaint(Component?component)?{
??? // ?事實(shí)上component可能是一個(gè)復(fù)合對(duì)象,因此該方法沒(méi)有區(qū)分基本對(duì)象和復(fù)合對(duì)象
???component.draw();
}
????????????????????????????????????????
上面的repaint()方法中,Component對(duì)象被作為參數(shù)傳遞到了函數(shù) 體,這個(gè)對(duì)象可以是Component,也可以是Composite。然后函數(shù)體中調(diào)用draw()方法。由于Composte類繼承了 Component,程序就不用區(qū)分傳入的參數(shù)到底是哪種類的實(shí)例,只需要調(diào)用該對(duì)象的draw()方法就可以了。
圖1中的類圖展示了復(fù)合模式的一個(gè)方面:開(kāi)發(fā)人員必須在引用一個(gè) Component對(duì)象時(shí)必須區(qū)分它到底了一個(gè)Component對(duì)象還是一個(gè)Composite對(duì)象。通常開(kāi)發(fā)人員可以在Component類中加入一 個(gè)方法,例如isComposite(),來(lái)辨別Composite類。如果該方法返回的是True,開(kāi)發(fā)人員就需要將Component對(duì)象強(qiáng)制轉(zhuǎn)換為 Compoiste對(duì)象。

if (component.isComposite())?{
???Composite?composite? = ?(Composite)component;
???composite.addComponent(AComponent);
}

圖2顯示了另一種實(shí)現(xiàn)復(fù)合模式的方法:

在圖2所示的復(fù)合模型中,開(kāi)發(fā)人員不必區(qū)分Component對(duì)象和Composite對(duì)象,也不需要將Component對(duì)象強(qiáng)制轉(zhuǎn)換Composited對(duì)象。這樣代碼3中的代碼就變成了一行:

component.addComponent(AComponent);

但是如果代碼4中的component不是Composite的實(shí)例, addComponent()方法會(huì)做些什么呢?這是圖2中的復(fù)合模式中最重要的一部分。顯然一個(gè)基本類型不可能包含其他基本類型或復(fù)合類型, Component.addComponent()可能什么也不干,或者拋出一個(gè)異常。通常我們認(rèn)為將基本類型加入其他基本類型的操作是一個(gè)錯(cuò)誤,因此拋 出異常是開(kāi)發(fā)人員最好的選擇。
那么這兩種復(fù)合模式的實(shí)現(xiàn)方法哪一個(gè)更好呢?這是一個(gè)常常引起爭(zhēng)論的問(wèn)題。我個(gè)人覺(jué)得第二種方法更好一些,因?yàn)殚_(kāi)發(fā)人員不必區(qū)分Component對(duì)象和Composite對(duì)象,也不用強(qiáng)制轉(zhuǎn)換對(duì)象。
使用復(fù)合模式的實(shí)際例子:Struct Tiles
下面讓我們來(lái)看一個(gè)復(fù)合模式應(yīng)用用到Apache Struts JSP框架中的例子。在Apache Struts的框架中包含了一個(gè)被稱為T(mén)iles的JSP標(biāo)簽庫(kù),它是你能夠?qū)⒍鄠€(gè)JSP組合成一個(gè)Web頁(yè)面。事實(shí)上,它實(shí)現(xiàn)J2EE復(fù)合視圖模式。在 我們討論復(fù)合模式和Tiles標(biāo)簽庫(kù)的聯(lián)系前,讓我們先來(lái)看一個(gè)Tiles的例子。如果你對(duì)Struts已經(jīng)非常熟悉了,你可以跳到"在Struts Tiles中使用復(fù)合模式"一節(jié)。
通常一張網(wǎng)頁(yè)是有幾個(gè)區(qū)域構(gòu)成的。例如圖3中的網(wǎng)頁(yè)包含了邊欄、頭、內(nèi)容和角注四個(gè)部分。這四個(gè)部分在網(wǎng)頁(yè)上的分布構(gòu)成了布局。Struts Tiles使你能夠重用單獨(dú)的每個(gè)區(qū)域和布局。在我們討論重用的問(wèn)題之前,讓我們來(lái)看看怎樣用不同的方法實(shí)現(xiàn)圖3中的網(wǎng)頁(yè)。

1.手工實(shí)現(xiàn)布局
下面是相應(yīng)的HTML代碼:
<! DOCTYPE?HTML?PUBLIC?"-//W3C//DTD?HTML?4.0?Transitional//EN" >
<% @?page?contentType = ' text/html;?charset=UTF-8'?%>
< html >
??? < head >
?????? < title > Implementing?Complex?Layouts?by?Hand </ title >
??? </ head >
??? < body?background = ' graphics/blueAndWhiteBackground.gif'>
?????? < % -- ?One?table?lays?out?all?of?the?content? for ?this?page? -- %>
?????? < table? width ='100%'? height ='100%' >
????????? < tr >
???????????? <% -- ?Sidebar -- %>
???????????? < td? width ='150'? valign ='top'? align ='left' >
??????????????? < table >
?????????????????? < tr >
????????????????????? <% -- ?Sidebar?top? -- %>
????????????????????? < td? width ='150'? height ='65'? valign ='top'? align ='left' >
???????????????????????? < a? href ='' >
??????????????????????????? < img? src ='graphics/flags/britain_flag.gif' /></ a >
???????????????????????? < a? href ='' >
??????????????????????????? < img? src ='graphics/flags/german_flag.gif' /></ a >
???????????????????????? < a? href ='' >
??????????????????????????? < img? src ='graphics/flags/chinese_flag.gif' /></ a >
????????????????????? </ td >
?????????????????? </ tr >
?????????????????? < tr >
????????????????????? <% -- ?Sidebar?bottom? -- %>
????????????????????? < td >
???????????????????????? < font? size ='5' > Links </ font >< p >
???????????????????????? < a? href ='' > Home </ a >< br >
???????????????????????? < a? href ='' > Products </ a >< br >
???????????????????????? < a? href ='' > Downloads </ a >< br >
???????????????????????? < a? href ='' > White?papers </ a >< br >
???????????????????????? < a? href ='' > Contact?us </ a >< br >
????????????????????? </ td >
?????????????????? </ tr >
??????????????? </ table >
???????????? </ td >
???????????? <% -- ?Main?content -- %>
???????????? < td? valign ='top'? height ='100%'? width ='*' >
??????????????? < table? width ='100%'? height ='100%' >
?????????????????? < tr >
????????????????????? <% -- ?Header -- %>
????????????????????? < td? valign ='top'? height ='15%' >
???????????????????????? < font? size ='6' > Welcome?to?Sabreware,?Inc. </ font >
???????????????????????? < hr >
????????????????????? </ td >
?????????????????? < tr >
????????????????? < tr >
????????????????????? <% -- ?Content -- %>
????????????????????? < td? valign ='top'? height ='*' >
???????????????????????? < font? size ='4' > Page-specific?content?goes?here </ font >
????????????????????? </ td >
?????????????????? </ tr >
?????????????????? < tr >
????????????????????? <% -- ?Footer -- %>
????????????????????? < td? valign ='bottom'? height ='15%' >
???????????????????????? < hr >
????????????????????????Thanks?for?stopping?by!
????????????????????? </ td >
?????????????????? </ tr >
??????????????? </ table >
???????????? </ td >
????????? </ tr >
?????? </ table >
??? </ body >
</ html >
然后你需要實(shí)現(xiàn)JSP。根據(jù)代碼5編寫(xiě)出的JSP代碼有兩個(gè)缺點(diǎn)。第一,頁(yè)面的內(nèi)容
被嵌入了JSP代碼中,因此開(kāi)發(fā)人員無(wú)法重用它。而事實(shí)上,邊欄、頭和角注在一個(gè)網(wǎng)站的網(wǎng)頁(yè)中可能會(huì)被多次重用。第二,頁(yè)面的布局也被嵌入到JSP代碼
中,因此即使有很多網(wǎng)也采用同樣的布局,開(kāi)發(fā)人員也無(wú)法重用它。通過(guò)使用
2.使用
在下面的例子中,我們使用
<% @?page?contentType = ' text/html;?charset=UTF-8'?%>
< html >
??? < head >
?????? < title > Implementing?Complex?Layouts?by?Hand </ title >
??? </ head >
??? < body?background = ' graphics/blueAndWhiteBackground.gif'>
?????? < % -- ?One?table?lays?out?all?of?the?content? for ?this?page? -- %>
?????? < table? width ='100%'? height ='100%' >
????????? < tr >
???????????? <% -- ?Sidebar?section? -- %>
???????????? < td? width ='150'? valign ='top'? align ='left' >
??????????????? < jsp:include? page ='sidebar.jsp' />
???????????? </ td >
???????????? <% -- ?Main?content?section? -- %>
???????????? < td? height ='100%'? width ='*' >
??????????????? < table? width ='100%'? height ='100%' >
?????????????????? < tr >
????????????????????? <% -- ?Header?section? -- %>
????????????????????? < td? valign ='top'? height ='15%' >
???????????????????????? < jsp:include? page ='header.jsp' />
????????????????????? </ td >
?????????????????? < tr >
?????????????????? < tr >
????????????????????? <% -- ?Content?section? -- %>
????????????????????? < td? valign ='top'? height ='*' >
???????????????????????? < jsp:include? page ='content.jsp' />
????????????????????? </ td >
?????????????????? </ tr >
?????????????????? < tr >
????????????????????? <% -- ?Footer?section? -- %>
????????????????????? < td? valign ='bottom'? height ='15%' >
???????????????????????? < jsp:include? page ='footer.jsp' />
????????????????????? </ td >
?????????????????? </ tr >
??????????????? </ table >
???????????? </ td >
????????? </ tr >
?????? </ table >
??? </ body >
</ html >
在上面的代碼中,通過(guò)使用
<% @?page?contentType = ' text/html;?charset=UTF-8'?%>
< table?width = ' 100%'>
??? < tr >
?????? < % -- ?Sidebar?top?component? -- %>
?????? < td? width ='150'? height ='65'? valign ='top'? align ='left' >
???????? < a? href ='' >< img? src ='graphics/flags/britain_flag.gif' /></ a >
???????? < a? href ='' >< img? src ='graphics/flags/german_flag.gif' /></ a >
???????? < a? href ='' >< img? src ='graphics/flags/chinese_flag.gif' /></ a >
?????? </ td >
??? </ tr >
??? < tr >
?????? <% -- ?Sidebar?bottom?component? -- %>
?????? < td >
????????? < table >
???????????? < tr >
??????????????? < td >
?????????????????? < font? size ='5' > Links </ font >< p >
?????????????????? < a? href ='' > Home </ a >< br >
?????????????????? < a? href ='' > Products </ a >< br >
?????????????????? < a? href ='' > Downloads </ a >< br >
?????????????????? < a? href ='' > White?papers </ a >< br >
?????????????????? < a? href ='' > Contact?us </ a >< br >
??????????????? </ td >
???????????? </ tr >
????????? </ table >
?????? </ td >
??? </ tr >
</ table >
????????
< font? size ='6' > Welcome?to?Sabreware,?Inc. </ font >
< hr >
代碼9?content.jsp
< font? size ='4' > Page-specific?content?goes?here </ font >
代碼10?footer.jsp
< hr >
Thanks?for?stopping?by!
3.利用Structs Tiles來(lái)實(shí)現(xiàn)布局
代碼10中展示了如何用Struts Tiles來(lái)實(shí)現(xiàn)前面提到的網(wǎng)頁(yè)。這段代碼利用了
<% @?page?contentType = ' text/html;?charset=UTF-8'?%>
< %@?taglib?uri = ' WEB-INF/tlds/struts-tiles.tld'?prefix='tiles'?%>
< tiles:insert?definition = ' sidebar-header-footer-definition'/>
代碼11?struts - tiles.tld
< !DOCTYPE?tiles - definitions? PUBLIC
?? " -//Apache?Software?Foundation//DTD?Tiles?Configuration//EN "
?? " http://jakarta.apache.org/struts/dtds/tiles-config.dtd " >
< tiles - definitions >
??? < definition??name = ' sidebar-header-footer-definition'?
????????????????path = ' header-footer-sidebar-layout.jsp'>
?????? < put?name = ' sidebar'?value='sidebar.jsp'/>
?????? < put?name = ' header'??value='header.jsp'/>???
?????? < put?name = ' content'?value='content.jsp'/>???
?????? < put?name = ' footer'??value='footer.jsp'/>???
??? </ definition >
</ tiles - definitions >
在struts-tiles.tld中可以看到,網(wǎng)頁(yè)的布局被封裝在header- footer-sidebar-layout.jsp中,而網(wǎng)頁(yè)的內(nèi)容被封裝在sidebar.jsp, header.jsp, content.jsp和footer.jsp中(參見(jiàn)代碼7到代碼10)。代碼12列出了封裝了網(wǎng)頁(yè)布局的JSP。
<! DOCTYPE?HTML?PUBLIC?"-//W3C//DTD?HTML?4.0?Transitional//EN" >
<% @?page?contentType = ' text/html;?charset=UTF-8'?%>
< html >
??? < head >
?????? < title > Struts?Tiles?implements?the?Composite?pattern </ title >
??? </ head >
??? < body?background = ' graphics/blueAndWhiteBackground.gif'>
?????? < %@?taglib?uri = ' /WEB-INF/tlds/struts-tiles.tld'?
??????????????prefix = ' tiles'%>
?????? < % -- ?One?table?lays?out?all?of?the?content? -- %>
?????? < table? width ='100%'? height ='100%' >
????????? <% -- ?Sidebar?section? -- %>
????????? < tr >
???????????? < td? width ='150'? valign ='top'? align ='left' >
??????????????? < tiles:insert? attribute ='sidebar' />
???????????? </ td >
???????????? <% -- ?Main?content?section? -- %>
???????????? < td? valign ='top'? height ='100%'? width ='*' >
??????????????? < table? width ='100%'? height ='100%' >
?????????????????? < tr >
????????????????????? <% -- ?Header?section? -- %>
????????????????????? < td? height ='15%' >
???????????????????????? < tiles:insert? attribute ='header' />
????????????????????? </ td >
?????????????????? < tr >
?????????????????? < tr >
????????????????????? <% -- ?Content?section? -- %>
????????????????????? < td? valign ='top'? height ='*' >
???????????????????????? < tiles:insert? attribute ='content' />
????????????????????? </ td >
?????????????????? </ tr >
?????????????????? < tr >
????????????????????? <% -- ?Footer?section? -- %>
????????????????????? < td? valign ='bottom'? height ='15%' >
???????????????????????? < tiles:insert? attribute ='footer' />
????????????????????? </ td >
?????????????????? </ tr >
??????????????? </ table >
???????????? </ td >
????????? </ tr >
?????? </ table >
??? </ body >
</ html >
如果你希望改變網(wǎng)頁(yè)的內(nèi)容,你可以定義另外一個(gè)Tile(如代碼13、14),更改其中與內(nèi)容相關(guān)的部分,但是保留網(wǎng)頁(yè)原有的布局,這樣重用布局和重用內(nèi)容的問(wèn)題都解決了。
< tiles-definitions >
??? < definition?? name ='a-different-sidebar-header-footer-definition'?
???????????????? path ='header-footer-sidebar-layout.jsp' >
?????? < put? name ='sidebar'? value ='sidebar.jsp' />
?????? < put? name ='header'?? value ='header.jsp' /> ???
?????? < put? name ='content'? value ='someOtherContent.jsp' /> ???
?????? < put? name ='footer'?? value ='footer.jsp' /> ???
??? </ definition >
</ tiles-definitions >
????????????????
然后在
|
Struct Tiles實(shí)現(xiàn)了復(fù)合模式,在Struct Tiles中,JSP就是圖1和圖2中提到的Component類,而Tiles的定義代表了Composite類。這使開(kāi)發(fā)人員能夠指定一個(gè)JSP文件 或一個(gè)Tiles定義作為JSP頁(yè)面上某個(gè)區(qū)域中的內(nèi)容。代碼15展示了這個(gè)功能:
<! DOCTYPE?tiles-definitions?PUBLIC
??"-//Apache?Software?Foundation//DTD?Tiles?Configuration//EN"
??"http://jakarta.apache.org/struts/dtds/tiles-config.dtd" >
< tiles-definitions >
??? < definition?? name ='sidebar-definition'?
???????????????? path ='sidebar-layout.jsp' >
?????? < put? name ='top'???? value ='flags.jsp' />
?????? < put? name ='bottom'? value ='sidebar-links.jsp' />
??? </ definition >
??? < definition?? name ='sidebar-header-footer-definition'?
???????????????? path ='header-footer-sidebar-layout.jsp' >
?????? < put? name ='sidebar'? value ='sidebar-definition'
??????????? type ='definition' />
?????? < put? name ='header'?? value ='header.jsp' />
?????? < put? name ='content'? value ='content.jsp' />
?????? < put? name ='footer'?? value ='footer.jsp' />
??? </ definition >
</ tiles-definitions >
在上面的代碼中定義了兩個(gè)Tile:sidebar-definition和 sidebar-header-footer-definition。sidebar-definition被指定為sidebar-header- footer-definition中Value屬性的值。這是一個(gè)很典型的復(fù)合模式的應(yīng)用。在前面的一些例子中,Value屬性的值通常是一個(gè)JSP文 件,而在這里,Value屬性的值是另一個(gè)Tile的定義。
復(fù)合模式在與界面相關(guān)的設(shè)計(jì)中被經(jīng)常使用到,最明顯的例子就是Swing和Struts。由于它能夠使你以同樣的方式對(duì)待部件和包含部件的容器,因此在某些情況下可以大大提高代碼的可重用度,提高開(kāi)發(fā)的效率