1. 程式人生 > >研磨設計模式 之 代理模式(Proxy)1——跟著cc學設計系列

研磨設計模式 之 代理模式(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】

---------------------------------------------------------------------------