• 设计模式之代理模式(Proxy)


    1.场景

    在一个HR(人力资源)应用项目中客户提出,当选择一个部门或是分公司的时候,要把这个部门或者分公司下的所有员工都显示出来,而且不使用分页,方便他们进行业务处理。在显示全部员工的时候, 只需要显示姓名即可,但是也需要提供如下功能:在必要的时候可以选择并查看某位员工的详细信息(user表中的所有字段)。

    实现起来也非常简单,只需要查询对应deptid下的user表就可以了(这样进行的查询是全表查询,也就是会查询表中的所有字段)。但是实现看似简单,功能也正确,但是蕴涵了一个比较大的问题。那就是,一次性访问的数据条数过多,而且每条描述的数据量又很大,这样操作将会消耗较多的内存。而从用户的角度来说,有很大的随机性。客户有可能访问每一条客户端详细信息,也有可能一条都不访问。也就是说,一次性访问很多条数据,消耗了大量内存,但是消耗的内存很有可能是浪费掉的。客户有可能根本就不会去访问那么多数据,对于每条数据,客户只需要查看姓名而已。

    那么该怎么实现,才能既把用户数据的姓名显示出来,而又能节省内存空间?当然还要实现在客户想要看到更多数据的时候,能够正确访问到数据呢?这就是我们接下来要讲的代理模式。

    2.解决方案

    2.1.代理模式的定义

    为其他对象提供一种代理以控制对这个对象的访问。

    2.2.代理模式的结构和说明

    • Proxy:代理对象,通常具有如下功能。实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用(代理对象中有具体对象的成员变量),可以在需要的时候调用具体的目标对象。可以控制对具体对象的访问,并可以负责创建和删除它。
    • Subject:具体接口,定义代理和具体对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。
    • RealSubject:具体的目标对象,真正实现目标接口要求的功能。

    在运行时一种可能的代理结构的对象图如下图所示

    也就是在客户首先见到的是Subject接口。接口调用代理Proxy,然后代理再调用具体对象RealSubject。

    3.使用代理重写实例

    (1)用户数据对象接口,就是对用户数据对象属性进行操作的getter/setter方法。

    UserModelApi.java

    View Code
    public interface UserModelApi {
        public String getUserId() ;
        public void setUserId(String userId);
        
        public String getUserName() ;
        public void setUserName(String userName) ;
        
        public String getDeptId() ;
        public void setDeptId(String deptId);
        
        public String getSex() ;
        public void setSex(String sex) ;
    
    }

    (2)定义了接口,需要让UserModel来实现它。

    UserModel.java

    View Code
    /**
     * 描述用户数据的对象
     */
    public class UserModel implements UserModelApi {
        private String userId;//用户编号
        private String userName;//用户姓名
        private String deptId;//部门编号
        private String sex;//性别
        public String getUserId() {
            return userId;
        }
        public void setUserId(String userId) {
            this.userId = userId;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getDeptId() {
            return deptId;
        }
        public void setDeptId(String deptId) {
            this.deptId = deptId;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        //重写Object类的toString()方法
        @Override
        public String toString(){
            return "userId="+userId+",name="+userName+",depId="+deptId+",sex="+sex+"\n";
        }
    }

    (3)接下来定义代理对象

    Proxy.java

    View Code
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    /**
     * 代理对象,代理用户数据对象
     */
    public class Proxy implements UserModelApi {
        /**
         * 持有被代理的具体的目标对象
         */
        private UserModel realSubject = null;
        private boolean loaded = false;//标志位,表示是否被加载过
        /**
         * 构造方法,传入被代理的具体的目标对象
         * @param realSubject 被代理的具体的目标对象
         */
        public Proxy(UserModel realSubject) {
            this.realSubject = realSubject;
        }
        // 代理对象中只保存userId和userName属性,其他属性要从实体对象中获取
        @Override
        public String getUserId() {
            // TODO Auto-generated method stub
            return realSubject.getUserId();
        }
        @Override
        public void setUserId(String userId) {
            // TODO Auto-generated method stub
            realSubject.setUserId(userId);
        }
        @Override
        public String getUserName() {
            // TODO Auto-generated method stub
            return realSubject.getUserName();
        }
        @Override
        public void setUserName(String userName) {
            // TODO Auto-generated method stub
            realSubject.setUserName(userName);
        }
        @Override
        public String getDeptId() {
            // TODO Auto-generated method stub
            // 获取的非proxy对象中拥有的属性,则需要再次查询数据库获取
            if (!loaded)// 判断数据是否加载过。
            {
                reload();
                this.loaded = true;
            }
            return realSubject.getDeptId();
        }
        @Override
        public void setDeptId(String deptId) {
            // TODO Auto-generated method stub
            realSubject.setDeptId(deptId);
        }
        @Override
        public String getSex() {
            // TODO Auto-generated method stub
            if (!loaded) {
                reload();
                this.loaded = true;
            }
            return realSubject.getSex();
        }
        @Override
        public void setSex(String sex) {
            // TODO Auto-generated method stub
            realSubject.setSex(sex);
        }
        /**
         * 重新查询数据库以获取完整的用户数据
         */
        private void reload() {
            System.out.println("重新查询数据库获取完整的用户数据,userId=="
                    + realSubject.getUserId());
            Connection conn = null;
            try {
                conn = DBConnection.getInstance().getConnection();
                String sql = "select * from tbl_user where userid=?";
                PreparedStatement ps = conn.prepareStatement(sql);
                ps.setString(1, realSubject.getUserId());
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    //只需要重新获取除了userId和name外的数据
                    realSubject.setDeptId(rs.getString("deptid"));
                    realSubject.setSex(rs.getString("sex"));
                }
                rs.close();
                ps.close();
            } catch (Exception err) {
                err.printStackTrace();
            } finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public String toString(){
            return "userId="+getUserId()+",name="+getUserName()
            +",depId="+getDeptId()+",sex="+getSex()+"\n";
        }
    }

    (4)定义数据库连接对象

    DBConnection.java

    View Code
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    public class DBConnection {
        //数据库连接单例,饿汉式
        private static DBConnection instance=new DBConnection();
        //静态代码块,用户注册数据库连接驱动
        static {
            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }    
        }
        //获取数据库连接实例。
        public static DBConnection getInstance() {
            return instance;
        }
        /**
         * 获取数据库连接,可以直接通过
         * DBConnection.getInstance().getConnection();
         * 来获取数据库连接
         */
        public Connection getConnection() throws SQLException 
        {
            return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        }
    }

    (5)在UserManager对象中,不再对表进行全表查询,而只查询userid和username字段。

    UserManager.java

    View Code
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.ArrayList;
    import java.util.Collection;
    /**
     * 实现示例要求的功能
     */
    public class UserManager {
        /**
         * 根据部门编号来获取该部门下的所有人员
         * @param depId 部门编号
         * @return 该部门下的所有人员
         */
        public Collection<UserModelApi> getUserById(String deptId) throws Exception {
            Collection<UserModelApi> col = new ArrayList<UserModelApi>();
            Connection conn = null;
            try {
                conn = DBConnection.getInstance().getConnection();
                //只需要查询userId和name两个值就可以了
                String sql = "select u.userid,u.name from tbl_user u ,tbl_dept d where u.deptid=d.deptid and u.deptid LIKE ?";
                PreparedStatement ps = conn.prepareStatement(sql);
                ps.setString(1, deptId+"%");
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    //这里是创建的代理对象,而不是直接创建UserModel的对象
                    Proxy proxy=new Proxy(new UserModel());
                    //只是设置userId和name两个值就可以了
                    proxy.setUserId(rs.getString("userid"));
                    proxy.setUserName(rs.getString("name"));
                    col.add(proxy);
                }
                rs.close();
                ps.close();
            } finally {
                conn.close();
            }
            return col;
        }
    }

    (6)测试客户端

    Client.java

    View Code
    import java.util.Collection;
    
    public class Client {
    
        /**
         * @param args
         * @throws Exception 
         */
        public static void main(String[] args) throws Exception {
            // TODO Auto-generated method stub
            UserManager userManager=new UserManager();
            Collection<UserModelApi> col=userManager.getUserById("0101");
            //如果只是显示用户名称,那么不需要重新查询数据库
            for(UserModelApi umApi : col){
                System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getUserName());
            }        
            //如果访问非用户编号和用户姓名外的属性,那就会重新查询数据库
            for(UserModelApi umApi : col){
                System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getUserName()+",所属部门:="+umApi.getDeptId());
            }
        }
    }

    (7)运行结果:

    用户编号:=user0001,用户姓名:=张三1
    用户编号:=user0002,用户姓名:=张三2
    用户编号:=user0003,用户姓名:=张三3
    重新查询数据库获取完整的用户数据,userId==user0001
    用户编号:=user0001,用户姓名:=张三1,所属部门:=010101
    重新查询数据库获取完整的用户数据,userId==user0002
    用户编号:=user0002,用户姓名:=张三2,所属部门:=010101
    重新查询数据库获取完整的用户数据,userId==user0003
    用户编号:=user0003,用户姓名:=张三3,所属部门:=010102

    (8)总结说明:

    如果只是访问用户编号和用户信命的数据,是不需要重新查询数据库的。只有当访问到这两个数据以外的数据的时候,才需要重新查询数据库以获得完整的数据。

    (9)1+N次查询

    上述实例存在一个潜在的问题,那就是如果客户对每条用户数据都要求查看详细数据的话,那么总的查询数据库的次数会是1+N次。

    第一次查询,获得了N条数据的用户编号与姓名,然后展示给客户看。如果这个时候,客户对每条数据都点击查看详细信息的话,那么每一条数据都需要重新查询数据库,那么最后总的查询数据库的次数就是1+N次了。

    ps:2012-4-11

    本文实例中使用的是mysql数据库,其中所用到的两张表的结构与内容如下:

    (1)tbl_user

    View Code
    DROP TABLE IF EXISTS `tbl_user`;
    CREATE TABLE `tbl_user` (
      `userid` varchar(20) NOT NULL DEFAULT '',
      `name` varchar(20) DEFAULT NULL,
      `deptid` varchar(20) DEFAULT NULL,
      `sex` varchar(20) DEFAULT NULL,
      PRIMARY KEY (`userid`),
      KEY `ref_key_deptid` (`deptid`),
      CONSTRAINT `ref_key_deptid` FOREIGN KEY (`deptid`) REFERENCES `tbl_dept` (`deptid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of tbl_user
    -- ----------------------------
    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', '');

    (2)tbl_dept

    View Code
    DROP TABLE IF EXISTS `tbl_dept`;
    CREATE TABLE `tbl_dept` (
      `deptid` varchar(20) NOT NULL DEFAULT '',
      `name` varchar(20) DEFAULT NULL,
      PRIMARY KEY (`deptid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of tbl_dept
    -- ----------------------------
    INSERT INTO tbl_dept VALUES ('01', '总公司');
    INSERT INTO tbl_dept VALUES ('0101', '一分公司');
    INSERT INTO tbl_dept VALUES ('010101', '开发部');
    INSERT INTO tbl_dept VALUES ('010102', '测试部');
    INSERT INTO tbl_dept VALUES ('0102', '二分公司');
    INSERT INTO tbl_dept VALUES ('010201', '开发部');
    INSERT INTO tbl_dept VALUES ('010202', '客服部');

    代码实例的类图结构如下图所示:

    作者:xwdreamer
    欢迎任何形式的转载,但请务必注明出处。
    分享到:
  • 相关阅读:
    4、Java基本数据类型
    3、Java 对象和类
    2、Java 基础语法标识符、修饰符、变量、 数组、枚举、关键字
    1、Java 开发环境配置
    近期目标
    Java泛型是什么?实战demo
    Java高级篇XML和正则表达式
    Java高级篇反射和注解
    Java高级篇 JVM
    JavaScript执行顺序
  • 原文地址:https://www.cnblogs.com/xwdreamer/p/2296921.html
Copyright © 2020-2023  润新知