轉自: CCIENET
自從J2EE出現以來,就大大簡化了在Java下的企業級開發。但是隨著J2EE越來越普遍
地被應用到各個領域中,開發者們漸漸意識到需要一種方法來標準化應用程序的開發過程,他們采用的方法是標準化應用程序的結構層。在結構層通常封裝了一些獨
立于業務邏輯的復雜技術,以便在業務邏輯和底層的架構之間建立起弱連接。無可否認,J2EE是一個很成功的技術,它為一些基本的任務提供了一致的標準,例
如數據庫連接、分布式應用程序等。但是使用J2EE并不能保證開發人員開發出成功的應用程序。有些人認為J2EE本身就是一種框架技術,但是這種認識是不
正確的,我們應該意識到J2EE并沒有提供一個能夠幫助開發人員開發出高質量應用程序的框架,因此很多有經驗的開發人員通過利用設計模式來彌補這一缺陷。
在開發人員的圈子中,大家通過相互交流在開發過程中所遇到的問題以及解決方法來豐
富整個圈子的經驗。而設計模式就是在這樣的情況下產生的。一個設計模式必然是針對某個特定的問題的,這個問題的解決方案以及這樣解決問題產生的后果。在本
文中我將討論復合模式的特點和它的應用。
復合模式(Composite Patten)介紹
在介紹復合模式前,我們需要定義一下什么是復合對象(Composite
Object)。復合對象是包含了其它對象的對象。例如,一幅圖由一些基本的對象組成,例如線、圓、矩形和文本等,因此圖就是復合對象。因為在Java
中,開發人員操作基本對象的方式和操作復合對象的方式常常相同,因此需要利用到復合模式。例如,線或文本等基本圖形對象都需要支持繪制、移動或縮放等功
能;而圖這種復合對象也需要支持相同的功能。在理想的情況下,我們希望對復合對象和基本對象以完全相同的方式完成這些操作,否則實現的代碼將會產生不必要
的復雜性,并且不易于維護和擴展。
那么什么是復合模式呢?將對象組織到樹結構中以表達部分整體的層次關系就實現了復合模式,它使程序能夠以相同的方式對待基本對象和復合對象。
在程序中實現復合模式并不難。復合類繼承一個代表基本類型的基類就可以了
。圖1顯示了一個表現復合模式思想的類圖。
圖1 復合模式的實現
在圖1中,Component是代表基本類型的基類(也可以是一個接口);
Composite是復合類。例如Component代表的是基本圖形元素的基類,而Composite代表的是圖;Operation1()和
Operation2()方法分別是移動和縮放操作。圖1中的Leaf類代表的是點、線或者圓等基本圖形元素。
針對Component類中的每個方法,Composite類都有相同名稱的方法
與之對應。Composite類保存了一個基本對象的集合。通常Composite類中的方法在實現時都會將集合中的對象遍歷一次,然后調用每個對象中相
應的方法。例如圖對象Drawing的draw()方法可能是這樣實現的:
//
?代碼1一個復合方法
public
?
void
?draw()?{
???
//
?I遍歷所有的對象
???
for
(
int
?i
=
0
;?i?
<
?getComponentCount();?
++
i)?{
??????
//
?獲得對對象的應用,調用它的draw方法
??????Component?component?
=
?getComponent(i);
??????component.draw();???
???}
由于Composite類繼承了Component類,因此你可以將一個Composite對象傳遞給需要Component對象作為參數的方法。例如:
//
?代碼2??repaint方法
public
?
void
?repaint(Component?component)?{
???
//
?事實上component可能是一個復合對象,因此該方法沒有區分基本對象和復合對象
???component.draw();
}
????????????????????????????????????????
上面的repaint()方法中,Component對象被作為參數傳遞到了函數
體,這個對象可以是Component,也可以是Composite。然后函數體中調用draw()方法。由于Composte類繼承了
Component,程序就不用區分傳入的參數到底是哪種類的實例,只需要調用該對象的draw()方法就可以了。
圖1中的類圖展示了復合模式的一個方面:開發人員必須在引用一個
Component對象時必須區分它到底了一個Component對象還是一個Composite對象。通常開發人員可以在Component類中加入一
個方法,例如isComposite(),來辨別Composite類。如果該方法返回的是True,開發人員就需要將Component對象強制轉換為
Compoiste對象。
????????????????????????????
//
?代碼3,區分Component和Composite
if
(component.isComposite())?{
???Composite?composite?
=
?(Composite)component;
???composite.addComponent(AComponent);
}
圖2顯示了另一種實現復合模式的方法:
圖2 另一種復合模式的實現方法
在圖2所示的復合模型中,開發人員不必區分Component對象和Composite對象,也不需要將Component對象強制轉換Composited對象。這樣代碼3中的代碼就變成了一行:
//
?代碼4,不區分Component和Composite
component.addComponent(AComponent);
但是如果代碼4中的component不是Composite的實例,
addComponent()方法會做些什么呢?這是圖2中的復合模式中最重要的一部分。顯然一個基本類型不可能包含其他基本類型或復合類型,
Component.addComponent()可能什么也不干,或者拋出一個異常。通常我們認為將基本類型加入其他基本類型的操作是一個錯誤,因此拋
出異常是開發人員最好的選擇。
那么這兩種復合模式的實現方法哪一個更好呢?這是一個常常引起爭論的問題。我個人覺得第二種方法更好一些,因為開發人員不必區分Component對象和Composite對象,也不用強制轉換對象。
使用復合模式的實際例子:Struct Tiles
下面讓我們來看一個復合模式應用用到Apache Struts
JSP框架中的例子。在Apache
Struts的框架中包含了一個被稱為Tiles的JSP標簽庫,它是你能夠將多個JSP組合成一個Web頁面。事實上,它實現J2EE復合視圖模式。在
我們討論復合模式和Tiles標簽庫的聯系前,讓我們先來看一個Tiles的例子。如果你對Struts已經非常熟悉了,你可以跳到"在Struts
Tiles中使用復合模式"一節。
通常一張網頁是有幾個區域構成的。例如圖3中的網頁包含了邊欄、頭、內容和角注四個部分。這四個部分在網頁上的分布構成了布局。Struts Tiles使你能夠重用單獨的每個區域和布局。在我們討論重用的問題之前,讓我們來看看怎樣用不同的方法實現圖3中的網頁。
圖3 例子網頁
1.手工實現布局
下面是相應的HTML代碼:
????????????????????代碼5?手工實現布局的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
>
然后你需要實現JSP。根據代碼5編寫出的JSP代碼有兩個缺點。第一,頁面的內容
被嵌入了JSP代碼中,因此開發人員無法重用它。而事實上,邊欄、頭和角注在一個網站的網頁中可能會被多次重用。第二,頁面的布局也被嵌入到JSP代碼
中,因此即使有很多網也采用同樣的布局,開發人員也無法重用它。通過使用可以避免第一個問題。
2.使用<jsp:include>實現布局
在下面的例子中,我們使用<jsp:include>來實現圖3中的網頁。
????????????????????????????????????????????代碼6?用jsp:include實現布局
<%
@?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
>
在上面的代碼中,通過使用<jsp:include>來
調用其它JSP。由于在sidebar.jsp、header.jsp、content.jsp、和footer.jsp中封裝了邊欄、頭、內容和角注,
因此開發人員可以重用這些元素。但是這種解決方案仍然無法重用網頁的布局。下面是sidebar.jsp、header.jsp、
content.jsp、和footer.jsp的代碼:
????????????????????????????????????????代碼7?sidebar.jsp
<%
@?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
>
????????
????????????????????????????代碼8?header.jsp
<
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來實現布局
代碼10中展示了如何用Struts Tiles來實現前面提到的網頁。這段代碼利用了<titles:insert>標
簽來創建圖3中對應的JSP網頁。該JSP文件被定義在名稱為sidebar-header-footer-definition的Tiles定義中,定
義信息保存在Tiles的配置文件中,在這個例子中,配置文件是WEB-INF/tlds/struts-tiles.tld。代碼11列出了該文件。
????????????????????????代碼10?使用Struts?Tiles來封裝布局信息
<%
@?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中可以看到,網頁的布局被封裝在header-
footer-sidebar-layout.jsp中,而網頁的內容被封裝在sidebar.jsp, header.jsp,
content.jsp和footer.jsp中(參見代碼7到代碼10)。代碼12列出了封裝了網頁布局的JSP。
????????????????????????????????????????代碼12?header-footer-sidebar-layout.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
>
如果你希望改變網頁的內容,你可以定義另外一個Tile(如代碼13、14),更改其中與內容相關的部分,但是保留網頁原有的布局,這樣重用布局和重用內容的問題都解決了。
????????????????????????????????????????????代碼13?另一個Tile的定義
<
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
>
????????????????
然后在<tiles:insert>標簽中使用新定義的Tile,如代碼14所示
代碼14 使用新定義的Tiles <%@ page contentType='text/html; charset=UTF-8' %> <%@ taglib uri='WEB-INF/tlds/struts-tiles.tld' prefix='tiles' %> <tiles:insert definition='a-different-sidebar-header-footer-definition'/>
|
在Struts Tiles中使用復合模式
Struct Tiles實現了復合模式,在Struct
Tiles中,JSP就是圖1和圖2中提到的Component類,而Tiles的定義代表了Composite類。這使開發人員能夠指定一個JSP文件
或一個Tiles定義作為JSP頁面上某個區域中的內容。代碼15展示了這個功能:
????????????????????????????????????????????代碼15在Struts?Tiles中使用復合模式
<!
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
>
在上面的代碼中定義了兩個Tile:sidebar-definition和
sidebar-header-footer-definition。sidebar-definition被指定為sidebar-header-
footer-definition中Value屬性的值。這是一個很典型的復合模式的應用。在前面的一些例子中,Value屬性的值通常是一個JSP文
件,而在這里,Value屬性的值是另一個Tile的定義。
小結
復合模式在與界面相關的設計中被經常使用到,最明顯的例子就是Swing和Struts。由于它能夠使你以同樣的方式對待部件和包含部件的容器,因此在某些情況下可以大大提高代碼的可重用度,提高開發的效率