研磨設計模式 之 代理模式(Proxy)1——跟著cc學設計系列
11.1 場景問題
11.1.1 訪問多條資料
考慮這樣一個實際應用:要一次性訪問多條資料。
這個功能的背景是這樣的;在一個HR(人力資源)應用專案中客戶提出,當選擇一個部門或是分公司的時候,要把這個部門或者分公司下的所有員工都顯示出來,而且不要翻頁,好方便他們進行業務處理。在顯示全部員工的時候,只需要顯示名稱即可,但是也需要提供如下的功能:在必要的時候可以選擇並檢視某位員工的詳細資訊。
客戶方是一個集團公司,有些部門或者分公司可能有好幾百人,不讓翻頁,也就是要求一次性的獲取這多條資料並展示出來。
該怎麼樣實現呢?
11.1.2 不用模式的解決方案
不就是要獲取某個部門或者某個分公司下的所有員工的資訊嗎?直接使用sql語句從資料庫中查詢就可以得到,示意性的SQL大致如下:
String sql = "select * from 使用者表,部門表 " +"where 使用者表.depId=部門表.depId " +"and 部門表.depId like '"+使用者選擇檢視的depId+"%'"; |
為了方便獲取某個部門或者某個分公司下的所有員工的資訊,設計部門編號的時候,是按照層級來進行編碼的,比如:上一級部門的編碼為“01”,那麼本級的編碼就是“0101”、“0102”……以此類推,下一級的編碼就是“010101”、“010102”……。
這種設計方式,從設計上看雖然不夠優雅,但是實用,像這種獲取某個部門或者某個分公司下的所有員工的資訊的功能,就不用遞迴去查找了,直接使用like,只要找到以該編號開頭的所有部門就可以了。
示例涉及到的表有兩個,一個是使用者表,一個是部門表。兩個表需要描述的欄位都較多,尤其是使用者表,多達好幾十個,為了示例簡潔,簡化後簡單的定義如下:
DROP TABLE TBL_USER CASCADE CONSTRAINTS ; DROP TABLE TBL_DEP CASCADE CONSTRAINTS ; CREATE TABLE TBL_DEP ( DEPID VARCHAR2(20) PRIMARY KEY, NAME VARCHAR2(20) ); CREATE TABLE TBL_USER ( USERID VARCHAR2(20) PRIMARY KEY, NAME VARCHAR2(20) , DEPID VARCHAR2(20) , SEX VARCHAR2(10) , CONSTRAINT TBL_USER_FK FOREIGN KEY(DEPID) REFERENCES TBL_DEP(DEPID) ); |
全部採用大寫,是基於Oracle開發的習慣。再來增加點測試資料,SQL如下:
INSERT INTO TBL_DEP VALUES('01','總公司'); INSERT INTO TBL_DEP VALUES('0101','一分公司'); INSERT INTO TBL_DEP VALUES('0102','二分公司'); INSERT INTO TBL_DEP VALUES('010101','開發部'); INSERT INTO TBL_DEP VALUES('010102','測試部'); INSERT INTO TBL_DEP VALUES('010201','開發部'); INSERT INTO TBL_DEP VALUES('010202','客服部'); INSERT INTO TBL_USER VALUES('user0001','張三1','010101','男'); INSERT INTO TBL_USER VALUES('user0002','張三2','010101','男'); INSERT INTO TBL_USER VALUES('user0003','張三3','010102','男'); INSERT INTO TBL_USER VALUES('user0004','張三4','010201','男'); INSERT INTO TBL_USER VALUES('user0005','張三5','010201','男'); INSERT INTO TBL_USER VALUES('user0006','張三6','010202','男'); COMMIT; |
準備好了表結構和測試資料,下面來看看具體的實現示例,為了示例的簡潔,直接使用JDBC來完成。
(1)先來定義描述使用者資料的物件,示例程式碼如下:
/** * 描述使用者資料的物件 */ public class UserModel { /** * 使用者編號 */ private String userId; /** * 使用者姓名 */ private String name; /** * 部門編號 */ private String depId; /** * 性別 */ private String sex; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepId() { return depId; } public void setDepId(String depId) { this.depId = depId; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String toString(){ return "userId="+userId+",name="+name+",depId=" +depId+",sex="+sex+"\n"; } } |
(2)接下來使用JDBC來實現要求的功能,示例程式碼如下:
/** * 實現示例要求的功能 */ public class UserManager { /** * 根據部門編號來獲取該部門下的所有人員 * @param depId 部門編號 * @return 該部門下的所有人員 */ public Collection<UserModel> getUserByDepId( String depId)throws Exception{ Collection<UserModel> col = new ArrayList<UserModel>(); Connection conn = null; try{ conn = this.getConnection(); String sql = "select * from tbl_user u,tbl_dep d " +"where u.depId=d.depId and d.depId like ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, depId+"%"); ResultSet rs = pstmt.executeQuery(); while(rs.next()){ UserModel um = new UserModel(); um.setUserId(rs.getString("userId")); um.setName(rs.getString("name")); um.setDepId(rs.getString("depId")); um.setSex(rs.getString("sex")); col.add(um); } rs.close(); pstmt.close(); }finally{ conn.close(); } return col; } /** * 獲取與資料庫的連線 * @return 資料庫連線 */ private Connection getConnection() throws Exception { Class.forName("你用的資料庫對應的JDBC驅動類"); return DriverManager.getConnection( "連線資料庫的URL", "使用者名稱", "密碼"); } } |
(3)寫個客戶端來測試看看,是否能滿足功能要求,示例程式碼如下:
public class Client { public static void main(String[] args) throws Exception{ UserManager userManager = new UserManager(); Collection<UserModel> col = userManager.getUserByDepId("0101"); System.out.println(col); } } |
執行結果如下:
[userId=user0001,name=張三1,depId=010101,sex=男 , userId=user0002,name=張三2,depId=010101,sex=男 , userId=user0003,name=張三3,depId=010102,sex=男 ] |
你還可以修改getUserByDepId的引數,試試傳遞不同的引數,然後再看看輸出的值,看看是否正確的實現了要求的功能。
11.1.3 有何問題
上面的實現看起來很簡單,功能也正確,但是蘊含一個較大的問題,那就是:當一次性訪問的資料條數過多,而且每條描述的資料量又很大的話,那會消耗較多的記憶體。
前面也說了,對於使用者表,事實上是有很多欄位的,不僅僅是示例的那麼幾個,再加上不使用翻頁,一次性訪問的資料就可能會有很多條。如果一次性需要訪問的資料較多的話,記憶體開銷會比較大。
但是從客戶使用角度來說,有很大的隨機性,客戶既可能訪問每一條資料,也可能一條都不訪問。也就是說,一次性訪問很多條資料,消耗了大量記憶體,但是很可能是浪費掉了,客戶根本就不會去訪問那麼多資料,對於每條資料,客戶只需要看看姓名而已。
那麼該怎麼實現,才能既把多條使用者資料的姓名顯示出來,而又能節省記憶體空間,當然還要實現在客戶想要看到更多資料的時候,能正確訪問到資料呢?
---------------------------------------------------------------------------
研磨設計討論群【252780326】
---------------------------------------------------------------------------