J2EE中的設計模式

zengbo0710發表於2007-05-21
  什麼是Design Patten?
  簡單來說,Design Patten 就是一個常用的方案。在我們的開發過程中,經常會遇到一些相同或者相近的問題,每次我們都會去尋找一個新的解決方法,為了節省時間提高效率,我們提供一些能夠解決這些常見問題的,被證實可行的方案,構成一個統一的資源庫。
  一個Design Patten描述了一個被證實可行的方案。這些方案非常普通,是有完整定義的最常用的模式。這些模式可以被重用,有良好的伸縮性,而這些Design Patten的優勢將在設計J2EE應用時得到體現。
  1. Model-View-Controller
  a. 問題
  如果開發一個企業級應用,只需要一種客戶端的話,那麼一切都非常容易解決。但真實情況是,我們必須面對執行在各種裝置上客戶端,象PDA,WAP瀏覽器以及執行在桌面上的瀏覽器,我們不得不開發不同的應用程式來處理來自不同客戶端的請求。資料訪問與現實將混淆在一起,可能會出現重複的資料訪問,導致整個開發週期沒有必要的延長。
  b. 建議的解決方法
  Model-View-Controller (MVC) 開發模式被證明是有效的處理方法之一。它可以分離資料訪問和資料表現。你可以開發一個有伸縮性的,便於擴充套件的控制器,來維護整個流程。如圖1所示為整個模式的結構。MVC模式可以被對映到多層企業級的J2EE應用上。
  §所有的企業資料以及商業邏輯可以作為模式。
  §檢視可以通過模式訪問資料,並根據客戶端的要求來顯示資料。檢視必須保證當模式改變的時候,資料顯示也必須同時改變。
  §控制器用來結合模式和檢視,把客戶端來的請求轉換成模式能夠理解並執行的請求,並且根據請求以及執行結果來決定下一次顯示那一個檢視。
  根據以上的邏輯,你可以象這樣建立一個應用:
  §應用的商業邏輯由MVC中的模式也就是EJB來表現。模式必須處理由控制器傳遞過來的對資料的訪問請求。
  §多個頁面組成了MVC中的檢視,這些檢視必須隨模式一起更新。
  §控制器是一系列接收使用者動作的物件,他們把使用者的請求轉換成模式可理解的請求,並決定顯示那一個頁面當模式處理完請求後。
  
  圖1
  c. 要點
  §MVC結構適用於那些多使用者的,可擴充套件的,可維護的,具有很高互動性的系統。
  §MVC可以很好的表達使用者的互動和系統模式。
  §很方便的用多個檢視來顯示多套資料,是系統很方便的支援其他新的客戶端型別。
  §程式碼重複達到最低。
  §由於分離了模式中的流控制和資料表現,可以分清開發者的責任,另外,也可以加快產品推向市場的時間。
  2. Front Controller
  a. 問題
  MVC給出了一個整個應用的鬆散的耦合架構。現在來看一下這樣一個經常發生的情況。在某一個應用中,使用者看到的檢視和他所做的操作密切相關。這是一些具有高度互動性的頁面,而這些頁面之間含有高度的依賴性。在沒有任何模式的時候,這個應用只是一個許多獨立的頁面的集合,維護和擴充套件變得異常困難。
  §當一個頁面移動後,其他含有這個頁面連結的檔案,都必須修改。
  §當有一系列頁面需要口令保護時,許多配置檔案需要修改,或者頁面需要包含新的標記。
  §當一個頁面需要一個新的表示層時,頁面中的標記要被重新安排。
  當這個系統變得複雜時,這些問題將變得更糟。如果用MVC來解決的話,就變成一個如何管理控制器和檢視之間互動的問題。
  b. 建議的解決方法
  前臺控制模式可以解決這個問題。這個模式中,所有的請求都被傳送到一個物件中。這個主要的物件將處理所有的請求,決定以後顯示那一個檢視,以及實現必要的安全需求。對於把檢視顯示以及其他功能實現集中到一個主要的物件中,將使修改變得很容易,對應用的修改,可以在所有檢視中反映出來。
  c. 要點
  §這個模式對於需要在多個含有動態資料的頁面之間進行復雜導航的系統來說,是很有效的。
  §這個模式對於要在所有頁面中都包含模板,轉換等的應用來說,也是很有效的。
  §由於檢視的選擇集中在前端控制器上,因此,檢視的導航變得更加容易理解和便於配置。
  §檢視重用和變更會更加容易。
  §檢視之間的複雜互動,使得控制器變得複雜。從而,當應用發展的時候,控制器將變得難以維護。不過,大部分情況下可以用XML對映來解決。
  §實現應用要求的安全性檢驗變得很簡單。
  §這個模式不適合小型的,只顯示靜態內容的應用。
  d. 樣例
  §RequestMappings.xml 檔案對映了傳入的請求,處理器以及下一個頁面。
  useRequestHandler="true"
  requiresSecurityCheck="true" nextScreen="screen2.jsp">
  com.blah1.blah2.blah3.request1Handler
  以上這個檔案是控制器的指定配置,控制器的程式碼如下:
  §FrontControllerImpl.java 利用上面的XML實現了控制器
  // 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"
  }
  }
  用這種方法實現的控制器將很容易維護,當應用有新的變動的時候,只要修改XML檔案就能解決了。前臺控制模式將使在檢視和控制器之前有複雜互動的J2EE應用變得簡單。
  3. Session Fa?ade
  a. 問題
  前臺控制給出了一個基於MVC的,能有效管理使用者與J2EE應用之間進行的複雜互動。這個模式可以使處理頁面的現實順序和使用者的併發請求變得簡單。並且使增加和改變頁面現實變得更加容易。
  另外一個常見的問題是,當EJB或者業務邏輯發生變化的時候,應用的客戶端也必須隨之改變。我們來看一下這個問題。
  一般來說,為了表現一個賬戶中的使用者,我們使用一個業務邏輯來表示賬戶中的資訊,象使用者名稱和口令,再用一個EJB來管理使用者的個人資訊,象愛好,語言等。當要建立一個新的賬號或者修改一個已經存在的賬號時,必須訪問包含賬號資訊的EJB,讀取個人資訊,修改並且儲存,這樣的一個流程。
  當然,這只是一個非常簡單的例子,實際情況可能比這個複雜的多,象檢視使用者定製了哪些服務,檢驗客戶信用卡的有效性,存放訂單等。在這個案例中,為了實現一個完整的流程,客戶端必須訪問賬戶EJB來完成一系列適當的工作。下面的例子顯示了一個Servlet客戶端如何來控制一個使用者訂單。
  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
  }
  }
  以上的程式碼顯示了一個單個的客戶端。如果這個應用支援多種客戶端的話,必須為每一個客戶端制定一種處理方法來完成工作流程。如果有一個EJB的實現流程需要改變的話,那麼所有的參與這個流程的客戶端都需要改變。如果不同的EJB之間的互動需要改變的話,所有的客戶端都必須知道這一點,如果流程中需要增加一個新的步驟的話,所有的客戶端也必須隨之修改。
  這樣一來,EJB和客戶端之間的改變變得非常困難。客戶端必須對每個EJB分開進行訪問,致使網路速度變慢。同樣,應用越複雜,麻煩越大。
  b. 建議的解決方法
  解決這個問題的方法是,把客戶端和他們使用的EJB分割開。建議適用Session Fa?ade模式。這個模式通過一個Session Bean,為一系列的EJB提供統一的介面來實現流程。事實上,當客戶端只是使用這個介面來觸發流程。這樣,所有關於EJB實現流程所需要的改變,都和客戶端無關。
  看下面這個例子。這段程式碼用來控制與客戶相關的訂單的處理方法。
  // 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程式碼將很容易實現。
  // 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
  }
  }
  就象上面顯示的,客戶端的邏輯變得非常簡單。流程中的任何改變只要修改模式中的一處地方就可以了。客戶端可以仍舊使用原來的介面,而不必做任何修改。同樣,這個模式可以用來響應其他處理器的流程處理。這讓你能用同樣的模式來處理不同客戶端的不同流程。在這個例子中,模式提供了很好的伸縮性和可維護性。
  c. 要點
  §既然這種模式不涉及到資料訪問,就應該用Session Bean來實現。
  §對於用簡單介面來實現複雜EJB的子系統來說,是一個理想的選擇。
  §這個模式不適用於無流程處理的應用。
  §這個模式可以減少客戶端於EJB之間的通訊和依賴。
  §所有和EJB有關的互動,都有同一個Session Bean來控制,可以減少客戶端對EJB的誤用。
  §這個模式可以使支援多型別客戶端變得更容易。
  §可以減少網路資料傳遞。
  §所有的伺服器端的實現細節都對客戶端隱藏,在改變發生後,客戶端不用重新發布。
  §這個模式可以同樣看成一個集中處理器來處理所有的安全或日誌紀錄。
  4. Data Access Object
  a. 問題
  目前為止,你看到的模型都是用來構建可伸縮的,易於維護的J2EE應用。這些模式儘可能的把應用在多個層上來實現。但是,還有一點必須強調:EJB的資料表現。它們包括象EJB這樣的資料庫語言。如果資料庫有改變的話,相應的SQL也必須改變,而EJB也必須隨之更新。
  這些常見問題就是:訪問資料來源的程式碼與EJB結合在一起,這樣致使程式碼很難維護。看以下的程式碼。
  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. 建議的解決方法
  為了解決這個問題,從而讓你能很方便的修改你的資料訪問。建議使用DAO模式。這個模式把資料訪問邏輯從EJB中拿出來放入獨立的介面中。結果是EJB保留自己的業務邏輯方法,在需要資料的時候,通過DAO來訪問資料庫。這樣的模式,在要求修改資料訪問的時候,只要更新DAO的物件就可以了。看以下的程式碼。
  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
  }
  現在你有了一個DAO物件,利用這個物件你可以訪問資料。再看以下的程式碼。
  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);
  }
  }
  任何資料來源的修改只要更新DAO就可以解決了。另外,為了支援應用能夠支援多個不同的資料來源型別,你可以開發多個DAO來實現,並在EJB的釋出環境中指定這些資料來源型別。在一般情況下,EJB可以通過一個Factory物件來得到DAO。用這種方法實現的應用,可以很容易的改變它的資料來源型別。
  c. 要點
  §這個模式分離了業務邏輯和資料訪問邏輯。
  §這種模式特別適用於BMP。過一段時間,這種方式同樣可以移植到CMP中。
  §DAOs可以在釋出的時候選擇資料來源型別。
  §DAOs增強了應用的可伸縮性,因為資料來源改變變得很容易。
  §DAOs對資料訪問沒有任何限制,甚至可以訪問XML資料。
  §使用這個模式將導致增加一些額外的物件,並在一定程度上增加應用的複雜性。(完)

 

相關文章