什么是Design Patten?
簡(jiǎn)單來(lái)說(shuō),Design Patten 就是一個(gè)常用的方案。 在我們的開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)遇到一些相同或者相近的問(wèn)題,每次我們都會(huì)去尋找一個(gè)新的解決方法,為了節(jié)省時(shí)間提高效率,我們提供一些能夠解決這些常見(jiàn)問(wèn)題的,被證實(shí)可行的方案,構(gòu)成一個(gè)統(tǒng)一的資源庫(kù)。
一個(gè)Design Patten描述了一個(gè)被證實(shí)可行的方案。這些方案非常普通,是有完整定義的最常用的模式。 這些模式可以被重用,有良好的伸縮性,而這些Design Patten的優(yōu)勢(shì)將在設(shè)計(jì)J2EE應(yīng)用時(shí)得到體現(xiàn)。
1. Model-View-Controller
a. 問(wèn)題
如果開(kāi)發(fā)一個(gè)企業(yè)級(jí)應(yīng)用,只需要一種客戶(hù)端的話(huà),那么一切都非常容易解決。但真實(shí)情況是,我們必須面對(duì)運(yùn)行在各種設(shè)備上客戶(hù)端,象PDA,WAP瀏覽器以及運(yùn)行在桌面上的瀏覽器,我們不得不開(kāi)發(fā)不同的應(yīng)用程序來(lái)處理來(lái)自不同客戶(hù)端的請(qǐng)求。數(shù)據(jù)訪問(wèn)與現(xiàn)實(shí)將混淆在一起,可能會(huì)出現(xiàn)重復(fù)的數(shù)據(jù)訪問(wèn),導(dǎo)致整個(gè)開(kāi)發(fā)周期沒(méi)有必要的延長(zhǎng)。
b. 建議的解決方法
Model-View-Controller (MVC) 開(kāi)發(fā)模式被證明是有效的處理方法之一。它可以分離數(shù)據(jù)訪問(wèn)和數(shù)據(jù)表現(xiàn)。你可以開(kāi)發(fā)一個(gè)有伸縮性的,便于擴(kuò)展的控制器,來(lái)維護(hù)整個(gè)流程。如圖1所示為整個(gè)模式的結(jié)構(gòu)。MVC模式可以被映射到多層企業(yè)級(jí)的J2EE應(yīng)用上。
§ 所有的企業(yè)數(shù)據(jù)以及商業(yè)邏輯可以作為模式。
§ 視圖可以通過(guò)模式訪問(wèn)數(shù)據(jù),并根據(jù)客戶(hù)端的要求來(lái)顯示數(shù)據(jù)。視圖必須保證當(dāng)模式改變的時(shí)候,數(shù)據(jù)顯示也必須同時(shí)改變。
§ 控制器用來(lái)結(jié)合模式和視圖,把客戶(hù)端來(lái)的請(qǐng)求轉(zhuǎn)換成模式能夠理解并執(zhí)行的請(qǐng)求,并且根據(jù)請(qǐng)求以及執(zhí)行結(jié)果來(lái)決定下一次顯示那一個(gè)視圖。
根據(jù)以上的邏輯,你可以象這樣建立一個(gè)應(yīng)用:
§ 應(yīng)用的商業(yè)邏輯由MVC中的模式也就是EJB來(lái)表現(xiàn)。模式必須處理由控制器傳遞過(guò)來(lái)的對(duì)數(shù)據(jù)的訪問(wèn)請(qǐng)求。
§ 多個(gè)頁(yè)面組成了MVC中的視圖,這些視圖必須隨模式一起更新。
§ 控制器是一系列接收用戶(hù)動(dòng)作的對(duì)象,他們把用戶(hù)的請(qǐng)求轉(zhuǎn)換成模式可理解的請(qǐng)求,并決定顯示那一個(gè)頁(yè)面當(dāng)模式處理完請(qǐng)求后。
c. 要點(diǎn)
§ MVC結(jié)構(gòu)適用于那些多用戶(hù)的,可擴(kuò)展的,可維護(hù)的,具有很高交互性的系統(tǒng)。
§ MVC可以很好的表達(dá)用戶(hù)的交互和系統(tǒng)模式。
§ 很方便的用多個(gè)視圖來(lái)顯示多套數(shù)據(jù),是系統(tǒng)很方便的支持其他新的客戶(hù)端類(lèi)型。
§ 代碼重復(fù)達(dá)到最低。
§ 由于分離了模式中的流控制和數(shù)據(jù)表現(xiàn),可以分清開(kāi)發(fā)者的責(zé)任,另外,也可以加快產(chǎn)品推向市場(chǎng)的時(shí)間。
2. Front Controller
a. 問(wèn)題
MVC給出了一個(gè)整個(gè)應(yīng)用的松散的耦合架構(gòu)。現(xiàn)在來(lái)看一下這樣一個(gè)經(jīng)常發(fā)生的情況。在某一個(gè)應(yīng)用中,用戶(hù)看到的視圖和他所做的操作密切相關(guān)。這是一些具有高度交互性的頁(yè)面,而這些頁(yè)面之間含有高度的依賴(lài)性。在沒(méi)有任何模式的時(shí)候,這個(gè)應(yīng)用只是一個(gè)許多獨(dú)立的頁(yè)面的集合,維護(hù)和擴(kuò)展變得異常困難。
§ 當(dāng)一個(gè)頁(yè)面移動(dòng)后,其他含有這個(gè)頁(yè)面鏈接的文件,都必須修改。
§ 當(dāng)有一系列頁(yè)面需要口令保護(hù)時(shí),許多配置文件需要修改,或者頁(yè)面需要包含新的標(biāo)記。
§ 當(dāng)一個(gè)頁(yè)面需要一個(gè)新的表示層時(shí),頁(yè)面中的標(biāo)記要被重新安排。
當(dāng)這個(gè)系統(tǒng)變得復(fù)雜時(shí),這些問(wèn)題將變得更糟。如果用MVC來(lái)解決的話(huà),就變成一個(gè)如何管理控制器和視圖之間交互的問(wèn)題。
b. 建議的解決方法
前臺(tái)控制模式可以解決這個(gè)問(wèn)題。這個(gè)模式中,所有的請(qǐng)求都被傳送到一個(gè)對(duì)象中。這個(gè)主要的對(duì)象將處理所有的請(qǐng)求,決定以后顯示那一個(gè)視圖,以及實(shí)現(xiàn)必要的安全需求。對(duì)于把視圖顯示以及其他功能實(shí)現(xiàn)集中到一個(gè)主要的對(duì)象中,將使修改變得很容易,對(duì)應(yīng)用的修改,可以在所有視圖中反映出來(lái)。
c. 要點(diǎn)
§ 這個(gè)模式對(duì)于需要在多個(gè)含有動(dòng)態(tài)數(shù)據(jù)的頁(yè)面之間進(jìn)行復(fù)雜導(dǎo)航的系統(tǒng)來(lái)說(shuō),是很有效的。
§ 這個(gè)模式對(duì)于要在所有頁(yè)面中都包含模板,轉(zhuǎn)換等的應(yīng)用來(lái)說(shuō),也是很有效的。
§ 由于視圖的選擇集中在前端控制器上,因此,視圖的導(dǎo)航變得更加容易理解和便于配置。
§ 視圖重用和變更會(huì)更加容易。
§ 視圖之間的復(fù)雜交互,使得控制器變得復(fù)雜。從而,當(dāng)應(yīng)用發(fā)展的時(shí)候,控制器將變得難以維護(hù)。不過(guò),大部分情況下可以用XML映射來(lái)解決。
§ 實(shí)現(xiàn)應(yīng)用要求的安全性檢驗(yàn)變得很簡(jiǎn)單。
§ 這個(gè)模式不適合小型的,只顯示靜態(tài)內(nèi)容的應(yīng)用。
d. 樣例
§ RequestMappings.xml 文件映射了傳入的請(qǐng)求,處理器以及下一個(gè)頁(yè)面
useRequestHandler="true"
requiresSecurityCheck="true"
nextScreen="screen2.jsp">
com.blah1.blah2.blah3.request1Handler
以上這個(gè)文件是控制器的指定配置,控制器的代碼如下:
§ FrontControllerImpl.java 利用上面的XML實(shí)現(xiàn)了控制器
// all required imports
// exceptions to be caught appropriately wherever applicable
public class FrontControllerImpl extends HttpServlet {
// all required declarations, definitions
private HashMap requestMappings;
public void init() {
// load the mappings from XML file into the hashmap
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String currentPage= request.getPathInfo();
// get all mapping info for "currentPage" from the hashmap
// if "securityCheckRequired = true", do the security check
// if "useRequestHandler = true", pass on the incoming request to the specified handler
// forward the results to the given "nextScreen"
}
}
用這種方法實(shí)現(xiàn)的控制器將很容易維護(hù),當(dāng)應(yīng)用有新的變動(dòng)的時(shí)候,只要修改XML文件就能解決了。前臺(tái)控制模式將使在視圖和控制器之前有復(fù)雜交互的J2EE應(yīng)用變得簡(jiǎn)單。
3. Session Facade
a. 問(wèn)題
前臺(tái)控制給出了一個(gè)基于MVC的,能有效管理用戶(hù)與J2EE應(yīng)用之間進(jìn)行的復(fù)雜交互。這個(gè)模式可以使處理頁(yè)面的現(xiàn)實(shí)順序和用戶(hù)的并發(fā)請(qǐng)求變得簡(jiǎn)單。并且使增加和改變頁(yè)面現(xiàn)實(shí)變得更加容易。
另外一個(gè)常見(jiàn)的問(wèn)題是,當(dāng)EJB或者業(yè)務(wù)邏輯發(fā)生變化的時(shí)候,應(yīng)用的客戶(hù)端也必須隨之改變。我們來(lái)看一下這個(gè)問(wèn)題。
一般來(lái)說(shuō),為了表現(xiàn)一個(gè)賬戶(hù)中的用戶(hù),我們使用一個(gè)業(yè)務(wù)邏輯來(lái)表示賬戶(hù)中的信息,象用戶(hù)名和口令,再用一個(gè)EJB來(lái)管理用戶(hù)的個(gè)人信息,象愛(ài)好,語(yǔ)言等。當(dāng)要?jiǎng)?chuàng)建一個(gè)新的賬號(hào)或者修改一個(gè)已經(jīng)存在的賬號(hào)時(shí),必須訪問(wèn)包含賬號(hào)信息的EJB,讀取個(gè)人信息,修改并且保存,這樣的一個(gè)流程。
當(dāng)然,這只是一個(gè)非常簡(jiǎn)單的例子,實(shí)際情況可能比這個(gè)復(fù)雜的多,象查看用戶(hù)定制了哪些服務(wù),檢驗(yàn)客戶(hù)信用卡的有效性,存放訂單等。在這個(gè)案例中,為了實(shí)現(xiàn)一個(gè)完整的流程,客戶(hù)端必須訪問(wèn)賬戶(hù)EJB來(lái)完成一系列適當(dāng)?shù)墓ぷ鳌O旅娴睦语@示了一個(gè)Servlet客戶(hù)端如何來(lái)控制一個(gè)用戶(hù)訂單。
A servlet that does the workflow required for placing an order
// all required imports;
// exceptions to be caught appropriately wherever applicable;
// This servlet assumes that for placing an order the account and
// credit status of the customer has to be checked before getting the
// approval and committing the order. For simplicity, the EJBs that
// represent the business logic of account, credit status etc are
// not listed
public class OrderHandlingServlet extends HttpServlet {
// all required declarations, definitions
public void init() {
// all inits required done here
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// other logic as required
// Get reference to the required EJBs
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
UserAccountHome acctHome = (UserAccountHome)
PortableRemoteObject.narrow(obj, UserAccountHome.class);
UserAccount acct = acctHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
CreditCheckHome creditCheckHome = (CreditCheckHome)
PortableRemoteObject.narrow(obj, CreditCheckHome.class);
CreditCheck credit = creditCheckHome.create();
obj = ctxt.lookup("java:comp/env/ejb/Approvals");
ApprovalsHome apprHome = (ApprovalsHome)
PortableRemoteObject.narrow(obj, ApprovalsHome.class);
Approvals appr = apprHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
CommitOrderHome orderHome = (CommitOrderHome)
PortableRemoteObject.narrow(obj, CommitOrderHome.class);
CommitOrder order = orderHome.create();
// Acquire the customer ID and order details;
// Now do the required workflow to place the order
int result = acct.checkStatus(customerId);
if(result != OK) {
// stop further steps
}
result = credit.checkCreditWorth(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
result = appr.getApprovals(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
// Everything OK; place the order
result = order.placeOrder(customerId, currentOrder);
// do further processing as required
}
}
以上的代碼顯示了一個(gè)單個(gè)的客戶(hù)端。如果這個(gè)應(yīng)用支持多種客戶(hù)端的話(huà),必須為每一個(gè)客戶(hù)端制定一種處理方法來(lái)完成工作流程。如果有一個(gè)EJB的實(shí)現(xiàn)流程需要改變的話(huà),那么所有的參與這個(gè)流程的客戶(hù)端都需要改變。如果不同的EJB之間的交互需要改變的話(huà),所有的客戶(hù)端都必須知道這一點(diǎn),如果流程中需要增加一個(gè)新的步驟的話(huà),所有的客戶(hù)端也必須隨之修改。
這樣一來(lái),EJB和客戶(hù)端之間的改變變得非常困難。客戶(hù)端必須對(duì)每個(gè)EJB分開(kāi)進(jìn)行訪問(wèn),致使網(wǎng)絡(luò)速度變慢。同樣,應(yīng)用越復(fù)雜,麻煩越大。
b. 建議的解決方法
解決這個(gè)問(wèn)題的方法是,把客戶(hù)端和他們使用的EJB分割開(kāi)。建議適用Session Fa?ade模式。這個(gè)模式通過(guò)一個(gè)Session Bean,為一系列的EJB提供統(tǒng)一的接口來(lái)實(shí)現(xiàn)流程。事實(shí)上,當(dāng)客戶(hù)端只是使用這個(gè)接口來(lái)觸發(fā)流程。這樣,所有關(guān)于EJB實(shí)現(xiàn)流程所需要的改變,都和客戶(hù)端無(wú)關(guān)。
看下面這個(gè)例子。這段代碼用來(lái)控制與客戶(hù)相關(guān)的訂單的處理方法。
// All imports required
// Exception handling not shown in the sample code
public class OrderSessionFacade implements SessionBean {
// all EJB specific methods like ejbCreate defined here
// Here is the business method that does the workflow
// required when a customer places a new order
public int placeOrder(String customerId, Details orderDetails)
throws RemoteException {
// Get reference to the required EJBs
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
UserAccountHome acctHome = (UserAccountHome)
PortableRemoteObject.narrow(obj, UserAccountHome.class);
UserAccount acct = acctHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
CreditCheckHome creditCheckHome = (CreditCheckHome)
PortableRemoteObject.narrow(obj, CreditCheckHome.class);
CreditCheck credit = creditCheckHome.create();
obj = ctxt.lookup("java:comp/env/ejb/Approvals");
ApprovalsHome apprHome = (ApprovalsHome)
PortableRemoteObject.narrow(obj, ApprovalsHome.class);
Approvals appr = apprHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
CommitOrderHome orderHome = (CommitOrderHome)
PortableRemoteObject.narrow(obj, CommitOrderHome.class);
CommitOrder order = orderHome.create();
// Now do the required workflow to place the order
int result = acct.checkStatus(customerId);
if(result != OK) {
// stop further steps
}
result = credit.checkCreditWorth(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
result = appr.getApprovals(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
// Everything OK; place the order
int orderId = order.placeOrder(customerId, currentOrder);
// Do other processing required
return(orderId);
}
// Implement other workflows for other order related functionalities (like
// updating an existing order, canceling an existing order etc.) in a
// similar way
}
在模式允許的情況下,Servlet代碼將很容易實(shí)現(xiàn)。
// all required imports
// exceptions to be caught appropriately wherever applicable
public class OrderHandlingServlet extends HttpServlet {
// all required declarations, definitions
public void init() {
// all inits required done here
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// other logic as required
// Get reference to the session facade
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/OrderSessionFacade");
OrderSessionFacadeHome facadeHome = (OrderSessionFacadeHome)
PortableRemoteObject.narrow(obj, OrderSessionFacadeHome.class);
OrderSessionFacade facade = facadeHome.create();
// trigger the order workflow
int orderId = facade.placeOrder(customerId, currentOrder);
// do further processing as required
}
}
就象上面顯示的,客戶(hù)端的邏輯變得非常簡(jiǎn)單。流程中的任何改變只要修改模式中的一處地方就可以了。客戶(hù)端可以仍舊使用原來(lái)的接口,而不必做任何修改。同樣,這個(gè)模式可以用來(lái)響應(yīng)其他處理器的流程處理。這讓你能用同樣的模式來(lái)處理不同客戶(hù)端的不同流程。在這個(gè)例子中,模式提供了很好的伸縮性和可維護(hù)性。
c. 要點(diǎn)
§ 既然這種模式不涉及到數(shù)據(jù)訪問(wèn),就應(yīng)該用Session Bean來(lái)實(shí)現(xiàn)。
§ 對(duì)于用簡(jiǎn)單接口來(lái)實(shí)現(xiàn)復(fù)雜EJB的子系統(tǒng)來(lái)說(shuō),是一個(gè)理想的選擇。
§ 這個(gè)模式不適用于無(wú)流程處理的應(yīng)用。
§ 這個(gè)模式可以減少客戶(hù)端于EJB之間的通信和依賴(lài)。
§ 所有和EJB有關(guān)的交互,都有同一個(gè)Session Bean來(lái)控制,可以減少客戶(hù)端對(duì)EJB的誤用。
§ 這個(gè)模式可以使支持多類(lèi)型客戶(hù)端變得更容易。
§ 可以減少網(wǎng)絡(luò)數(shù)據(jù)傳遞。
§ 所有的服務(wù)器端的實(shí)現(xiàn)細(xì)節(jié)都對(duì)客戶(hù)端隱藏,在改變發(fā)生后,客戶(hù)端不用重新發(fā)布。
§ 這個(gè)模式可以同樣看成一個(gè)集中處理器來(lái)處理所有的安全或日志紀(jì)錄。
4. Data Access Object
a. 問(wèn)題
目前為止,你看到的模型都是用來(lái)構(gòu)建可伸縮的,易于維護(hù)的J2EE應(yīng)用。這些模式盡可能的把應(yīng)用在多個(gè)層上來(lái)實(shí)現(xiàn)。但是,還有一點(diǎn)必須強(qiáng)調(diào):EJB的數(shù)據(jù)表現(xiàn)。它們包括象EJB這樣的數(shù)據(jù)庫(kù)語(yǔ)言。如果數(shù)據(jù)庫(kù)有改變的話(huà),相應(yīng)的SQL也必須改變,而EJB也必須隨之更新。
這些常見(jiàn)問(wèn)題就是:訪問(wèn)數(shù)據(jù)源的代碼與EJB結(jié)合在一起,這樣致使代碼很難維護(hù)。看以下的代碼。
An EJB that has SQL code embedded in it
// all imports required
// exceptions not handled in the sample code
public class UserAccountEJB implements EntityBean {
// All EJB methods like ejbCreate, ejbRemove go here
// Business methods start here
public UserDetails getUserDetails(String userId) {
// A simple query for this example
String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;
InitialContext ic = new InitialContext();
datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
Connection dbConnection = datasource.getConnection();
Statement stmt = dbConnection.createStatement();
ResultSet result = stmt.executeQuery(queryStr);
// other processing like creation of UserDetails object
result.close();
stmt.close();
dbConnection.close();
return(details);
}
}
b. 建議的解決方法
為了解決這個(gè)問(wèn)題,從而讓你能很方便的修改你的數(shù)據(jù)訪問(wèn)。建議使用DAO模式。這個(gè)模式把數(shù)據(jù)訪問(wèn)邏輯從EJB中拿出來(lái)放入獨(dú)立的接口中。結(jié)果是EJB保留自己的業(yè)務(wù)邏輯方法,在需要數(shù)據(jù)的時(shí)候,通過(guò)DAO來(lái)訪問(wèn)數(shù)據(jù)庫(kù)。這樣的模式,在要求修改數(shù)據(jù)訪問(wèn)的時(shí)候,只要更新DAO的對(duì)象就可以了。看以下的代碼。
A Data Access Object that encapsulates all data resource access code
// All required imports
// Exception handling code not listed below for simplicity
public class UserAccountDAO {
private transient Connection dbConnection = null;
public UserAccountDAO() {}
public UserDetails getUserDetails(String userId) {
// A simple query for this example
String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;
InitialContext ic = new InitialContext();
datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
Connection dbConnection = datasource.getConnection();
Statement stmt = dbConnection.createStatement();
ResultSet result = stmt.executeQuery(queryStr);
// other processing like creation of UserDetails object
result.close();
stmt.close();
dbConnection.close();
return(details);
}
// Other data access / modification methods pertaining to the UserAccountEJB
}
現(xiàn)在你有了一個(gè)DAO對(duì)象,利用這個(gè)對(duì)象你可以訪問(wèn)數(shù)據(jù)。再看以下的代碼。
An EJB that uses a DAO
// all imports required
// exceptions not handled in the sample code
public class UserAccountEJB implements EntityBean {
// All EJB methods like ejbCreate, ejbRemove go here
// Business methods start here
public UserDetails getUserDetails(String userId) {
// other processing as required
UserAccountDAO dao = new UserAccountDAO();
UserDetails details = dao.getUserDetails(userId);
// other processing as required
return(details);
}
}
任何數(shù)據(jù)源的修改只要更新DAO就可以解決了。另外,為了支持應(yīng)用能夠支持多個(gè)不同的數(shù)據(jù)源類(lèi)型,你可以開(kāi)發(fā)多個(gè)DAO來(lái)實(shí)現(xiàn),并在EJB的發(fā)布環(huán)境中指定這些數(shù)據(jù)源類(lèi)型。在一般情況下,EJB可以通過(guò)一個(gè)Factory對(duì)象來(lái)得到DAO。用這種方法實(shí)現(xiàn)的應(yīng)用,可以很容易的改變它的數(shù)據(jù)源類(lèi)型。
c. 要點(diǎn)
§ 這個(gè)模式分離了業(yè)務(wù)邏輯和數(shù)據(jù)訪問(wèn)邏輯。
§ 這種模式特別適用于BMP。過(guò)一段時(shí)間,這種方式同樣可以移植到CMP中。
§ DAOs可以在發(fā)布的時(shí)候選擇數(shù)據(jù)源類(lèi)型。
§ DAOs增強(qiáng)了應(yīng)用的可伸縮性,因?yàn)閿?shù)據(jù)源改變變得很容易。
§ DAOs對(duì)數(shù)據(jù)訪問(wèn)沒(méi)有任何限制,甚至可以訪問(wèn)XML數(shù)據(jù)。
§ 使用這個(gè)模式將導(dǎo)致增加一些額外的對(duì)象,并在一定程度上增加應(yīng)用的復(fù)雜性。