`
chjavach
  • 浏览: 464618 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

研磨设计模式 之 代理模式(Proxy)2——跟着cc学设计系列

 
阅读更多

 

11.2  解决方案

11.2.1  代理模式来解决

用来解决上述问题的一个合理的解决方案就是代理模式。那么什么是代理模式呢?

(1)代理模式定义

 

(2)应用代理模式来解决的思路

仔细分析上面的问题,一次性访问多条数据,这个可能性是很难避免的,是客户的需要。也就是说,要想节省内存,就不能从减少数据条数入手了,那就只能从减少每条数据的数据量上来考虑。

一个基本的思路如下:由于客户访问这多条用户数据的时候,基本上只需要看到用户的姓名,因此可以考虑刚开始从数据库查询返回的用户数据就只有用户编号和用户姓名,当客户想要详细查看某个用户的数据的时候,再次根据用户编号到数据库中获取完整的用户数据。这样一来,就可以在满足客户功能的前提下,大大减少对内存的消耗,只是每次需要重新查询一下数据库,算是一个以时间换空间的策略

可是该如何来表示这个只有用户编号和姓名的对象呢?它还需要实现在必要的时候访问数据库去重新获取完整的用户数据。

代理模式引入一个Proxy对象来解决这个问题,刚开始只有用户编号和姓名的时候,不是一个完整的用户对象,而是一个代理对象,当需要访问完整的用户数据的时候,代理会从数据库中重新获取相应的数据,通常情况下是当客户需要访问除了用户编号和姓名之外的数据的时候,代理才会重新去获取数据。

11.2.2  模式结构和说明

代理模式的结构如图11.1所示:

 

图11.1  代理模式的结构示意图

Proxy

       代理对象,通常具有如下功能:

  • 实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象
  • 保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象
  • 可以控制对具体目标对象的访问,并可能负责创建和删除它

Subject

       目标接口,定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象

RealSubject

       具体的目标对象,真正实现目标接口要求的功能。

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

 

图11.2  运行时刻一种可能的代理结构的对象图

11.2.3  代理模式示例代码

(1)先看看目标接口的定义,示例代码如下:

/**

 * 抽象的目标接口,定义具体的目标对象和代理公用的接口

 */

public interface Subject {

    /**

     * 示意方法:一个抽象的请求方法

     */

    public void request();

}

(2)接下来看看具体目标对象的实现示意,示例代码如下:

/**

 * 具体的目标对象,是真正被代理的对象

 */

public class RealSubject implements Subject{

    public void request() {

       //执行具体的功能处理

    }

}

(3)接下来看看代理对象的实现示意,示例代码如下:

/**

 * 代理对象

 */

public class Proxy implements Subject{

    /**

     * 持有被代理的具体的目标对象

     */

    private RealSubject realSubject=null;

    /**

     * 构造方法,传入被代理的具体的目标对象

     * @param realSubject 被代理的具体的目标对象

     */

    public Proxy(RealSubject realSubject){

       this.realSubject = realSubject;

    }

    public void request() {

       //在转调具体的目标对象前,可以执行一些功能处理

      

       //转调具体的目标对象的方法

       realSubject.request();

      

       //在转调具体的目标对象后,可以执行一些功能处理

    }

}

11.2.4  使用代理模式重写示例

       要使用代理模式来重写示例,首先就需要为用户对象定义一个接口,然后实现相应的用户对象的代理,这样在使用用户对象的地方,就使用这个代理对象就可以了。

这个代理对象,在起初创建的时候,只需要装载用户编号和姓名这两个基本的数据,然后在客户需要访问除这两个属性外的数据的时候,才再次从数据库中查询并装载数据,从而达到节省内存的目的,因为如果用户不去访问详细的数据,那么那些数据就不需要被装载,那么对内存的消耗就会减少。

先看看这个时候系统的整体结构,如图11.3所示:

 

图11.3  代理模式重写示例的系统结构示意图

       此时的UserManager类,充当了标准代理模式中的Client的角色,因为是它在使用代理对象和用户数据对象的接口。

       还是看看具体的代码示例,会更清楚。

(1)先看看新定义的用户数据对象的接口,非常简单,就是对用户数据对象属性操作的getter/setter方法,因此也没有必要去注释了,示例代码如下:

/**

 * 定义用户数据对象的接口

 */

public interface UserModelApi {

    public String getUserId();

    public void setUserId(String userId);

    public String getName();

    public void setName(String name);

    public String getDepId();

    public void setDepId(String depId);

    public String getSex();

    public void setSex(String sex);

}

(2)定义了接口,需要让UserModel来实现它。基本没有什么变化,只是要实现这个新的接口而已,就不去代码示例了。

(3)接下来看看新加入的代理对象的实现,示例代码如下:

/**

 * 代理对象,代理用户数据对象

 */

public class Proxy implements UserModelApi{

    /**

     * 持有被代理的具体的目标对象

     */

    private UserModel realSubject=null;

 

    /**

     * 构造方法,传入被代理的具体的目标对象

     * @param realSubject 被代理的具体的目标对象

     */

    public Proxy(UserModel realSubject){

       this.realSubject = realSubject;

    }

    /**

     * 标示是否已经重新装载过数据了

     */

    private boolean loaded = false;

 

    public String getUserId() {

       return realSubject.getUserId();

    }

    public void setUserId(String userId) {

       realSubject.setUserId(userId);

    }

    public String getName() {

       return realSubject.getName();

    }

    public void setName(String name) {

       realSubject.setName(name);

    }

    public void setDepId(String depId) {

       realSubject.setDepId(depId);

    }

    public void setSex(String sex) {

       realSubject.setSex(sex);

    }

    public String getDepId() {

       //需要判断是否已经装载过了

       if(!this.loaded){

           //从数据库中重新装载

           reload();

           //设置重新装载的标志为true

           this.loaded = true;

       }

       return realSubject.getDepId();

    }  

    public String getSex() {

       if(!this.loaded){

           reload();

           this.loaded = true;

       }

       return realSubject.getSex();

    }

    /**

     * 重新查询数据库以获取完整的用户数据

     */

    private void reload(){

       System.out.println("重新查询数据库获取完整的用户数据,userId=="

+realSubject.getUserId());

       Connection conn = null;

       try{

           conn = this.getConnection();

           String sql = "select * from tbl_user where userId=?";

 

           PreparedStatement pstmt = conn.prepareStatement(sql);

           pstmt.setString(1, realSubject.getUserId());        

           ResultSet rs = pstmt.executeQuery();

           if(rs.next()){

              //只需要重新获取除了userIdname外的数据

              realSubject.setDepId(rs.getString("depId"));

              realSubject.setSex(rs.getString("sex"));

           }

          

           rs.close();

           pstmt.close();

       }catch(Exception err){

           err.printStackTrace();

       }finally{

           try {

              conn.close();

           } catch (SQLException e) {

              e.printStackTrace();

           }

       }

    }

    public String toString(){

       return "userId="+getUserId()+",name="+getName()

                  +",depId="+getDepId()+",sex="+getSex()+"\n";

    }

    private Connection getConnection() throws Exception {

       Class.forName("你用的数据库对应的JDBC驱动类");

       return DriverManager.getConnection(

              "连接数据库的URL", "用户名", "密码");

    }

}

(3)看看此时UserManager的变化,大致如下:

  • 从数据库查询值的时候,不需要全部获取了,只需要查询用户编号和姓名的数据就可以了
  • 把数据库中获取的值转变成对象的时候,创建的对象不再是UserModel,而是代理对象,而且设置值的时候,也不是全部都设置,只是设置用户编号和姓名两个属性的值

示例代码如下:

/**

 * 实现示例要求的功能

 */

public class UserManager { 

    /**

     * 根据部门编号来获取该部门下的所有人员

     * @param depId 部门编号

     * @return 该部门下的所有人员

     */

    public Collection<UserModelApi> getUserByDepId(

String depId)throws Exception{

       Collection<UserModelApi> col =

new ArrayList<UserModelApi>();

       Connection conn = null;

       try{

           conn = this.getConnection();

           //只需要查询userId和name两个值就可以了

           String sql = "select u.userId,u.name "

              +"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的对象

              Proxy proxy = new Proxy(new UserModel());

              //只是设置userId和name两个值就可以了

              proxy.setUserId(rs.getString("userId"));

              proxy.setName(rs.getString("name"));

 

              col.add(proxy);

           }

 

           rs.close();

           pstmt.close();

       }finally{

           conn.close();

       }

       return col;

    }

    private Connection getConnection() throws Exception {

       Class.forName("你用的数据库对应的JDBC驱动类");

       return DriverManager.getConnection(

              "连接数据库的URL", "用户名", "密码");

    }

}

(4)写个客户端来测试看看,是否能正确实现代理的功能呢,示例代码如下:

public class Client {

    public static void main(String[] args) throws Exception{

       UserManager userManager = new UserManager();

       Collection<UserModelApi> col =

userManager.getUserByDepId("0101");

 

       //如果只是显示用户名称,那么不需要重新查询数据库

       for(UserModelApi umApi : col){

           System.out.println("用户编号:="+umApi.getUserId()

+",用户姓名:="+umApi.getName());

       }

       //如果访问非用户编号和用户姓名外的属性,那就会重新查询数据库

       for(UserModelApi umApi : col){

           System.out.println("用户编号:="+umApi.getUserId()

+",用户姓名:="+umApi.getName()

+",所属部门:="+umApi.getDepId());

       }

    }

}

运行结果如下:

用户编号:=user0001,用户姓名:=张三1

用户编号:=user0002,用户姓名:=张三2

用户编号:=user0003,用户姓名:=张三3

重新查询数据库获取完整的用户数据,userId==user0001

用户编号:=user0001,用户姓名:=张三1,所属部门:=010101

重新查询数据库获取完整的用户数据,userId==user0002

用户编号:=user0002,用户姓名:=张三2,所属部门:=010101

重新查询数据库获取完整的用户数据,userId==user0003

用户编号:=user0003,用户姓名:=张三3,所属部门:=010102

       仔细查看上面的结果数据会发现,如果只是访问用户编号和用户姓名的数据,是不需要重新查询数据库的,只有当访问到这两个数据以外的数据时,才需要重新查询数据库以获得完整的数据。这样一来,如果客户不访问除这两个数据以外的数据,那么就不需要重新查询数据库,也就不需要装载那么多数据,从而节省内存。

(5)1+N次查询

       看完上面的示例,可能有些朋友会发现,这种实现方式有一个潜在的问题,就是如果客户对每条用户数据都要求查看详细的数据的话,那么总的查询数据库的次数会是1+N次之多。

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

       从上面的分析可以看出,这种做法最合适的场景就是:客户大多数情况下只需要查看用户编号和姓名,而少量的数据需要查看详细数据。这样既节省了内存,又减少了操作数据库的次数。

       看到这里,可能会有朋友想起,Hibernate这类ORM的框架,在Lazy Load的情况下,也存在1+N次查询的情况,原因就在于,Hibernate的Lazy Load就是使用代理来实现的,具体的实现细节这里就不去讨论了,但是原理是一样的。

 

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

私塾在线学习网原创内容  跟着cc学设计系列 之 研磨设计模式

研磨设计讨论群【252780326】

原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/5262.html

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

 

3
8
分享到:
评论

相关推荐

    研磨设计模式源码

    《研磨设计模式源码》是一份非常宝贵的资源,它提供了设计模式的实践代码,帮助开发者深入理解并应用这些模式。设计模式是软件工程中经过长期实践总结出来的一套通用解决方案,它们描述了在特定场景下如何解决常见...

    研磨设计模式(完整带书签).part2.pdf

    第11章 代理模式(Proxy) 第12章 观察者模式(Observer) 第13章 命令模式(Command) 第14章 迭代器模式(Iterator) 第15章 组合模式(Composite) 第16章 模板方法模式(Template Method) 第17章 策略模式...

    设计模式之代理模式proxy

    **设计模式之代理模式(Proxy Pattern)** 设计模式是软件工程中的一种最佳实践,它是在特定情境下解决常见问题的模板。代理模式是其中一种行为设计模式,它的核心思想是为一个对象提供一个替身或者代理,以控制对...

    设计模式C++学习之代理模式(Proxy)

    代理模式是一种设计模式,它是结构型模式之一,主要用于在客户端和目标对象之间建立一个代理对象,以便控制对目标对象的访问。在C++中,代理模式可以用来为其他对象提供一种代理以控制对这个对象的访问,或者增加...

    c++-设计模式之代理模式(Proxy)

    代理模式(Proxy Pattern)是一种结构型设计模式,用于为其他对象提供一种代理以控制对这个对象的访问。代理模式通常用于保护、延迟加载、记录请求等场景,可以在不改变原有对象的情况下为其增加新的功能。 代理...

    研磨设计模式PDF

    《研磨设计模式》这本书是陈臣和王斌两位作者合作的成果,专注于讲解软件设计中的模式应用。设计模式是软件工程中的一种最佳实践,它总结了在特定上下文中解决问题的常见方法,使得开发者可以复用这些解决方案,提高...

    《java设计模式》课后习题模拟试题解答——刘伟.zip

    2. **结构型模式**:如适配器(Adapter)、装饰器(Decorator)、代理(Proxy)、桥接(Bridge)、组合(Composite)、外观(Facade)和享元(Flyweight)。这些模式处理对象的组合和关系,提供更好的结构和接口,...

    Android设计模式之代理模式(Proxy Pattern)

    代理模式是设计模式的一种,它的主要目的是在不改变原有对象的基础上,为一个对象提供额外的功能或者控制对这个对象的访问。在Android开发中,代理模式的应用尤为常见,尤其在处理复杂的业务逻辑、网络请求、界面...

    Java设计模式——代理设计模式(静态代理和动态代理)

    代理设计模式是其中的一种,它的主要作用是在不修改原有对象的基础上,为对象添加额外的功能或者控制对对象的访问。代理设计模式分为静态代理和动态代理两种类型。 ### 静态代理 静态代理是在编译时就已经确定了...

    设计模式之代理模式proxy.zip

    代理模式是一种常用的设计模式,它在软件开发中扮演着重要的角色。代理模式允许我们为一个对象创建一个代理,这个代理对象在客户端和目标对象之间起到中介的作用,可以增强或控制对目标对象的访问。代理模式的主要...

    研磨设计模式博文集

    2. **结构型模式**:这些模式关注如何将类和对象组合成更大的结构,如适配器模式(Adapter)、桥接模式(Bridge)、装饰器模式(Decorator)、外观模式(Facade)、享元模式(Flyweight)、组合模式(Composite)和...

    java 设计模式之代理模式(Proxy Pattern)实现代码及设计详解:动态代理模式、静态代理模式

    在这些模式中,代理模式(Proxy Pattern)是一种常用的结构型设计模式,它允许我们为一个对象创建一个代理,该代理对象在客户端和目标对象之间起到中介的作用,可以增加额外的功能或控制访问。 代理模式分为两种...

    X-gen PPT下载——《研磨设计模式》 实战

    2. **结构型模式**:如适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、桥接模式(Bridge)、组合模式(Composite)、外观模式(Facade)和享元模式(Flyweight)。这些模式处理对象之间的关系...

    设计模式实现——代理模式

    **设计模式实现——代理模式** 在软件工程中,设计模式是一种通用可重用的解决方案,它描述了在特定上下文中经常出现的问题以及该问题的解决方案。代理模式是设计模式的一种,它提供了一种对目标对象的间接访问方式...

    研磨设计模式 源代码

    《研磨设计模式》是一本深入探讨软件设计模式的经典书籍,源代码包含了书中所讲解的各种设计模式的实际应用示例。设计模式是软件工程中的重要概念,它们是经过反复验证、在特定情境下解决常见问题的有效解决方案。...

    设计模式——代理模式

    代理模式是一种常用的设计模式,它在软件开发中扮演着重要的角色。代理模式的主要思想是为一个对象提供一个替身或代表,以便控制对该对象的访问。这个代理对象在客户端和目标对象之间起到中介的作用,可以增加额外的...

    设计模式之代理模式Proxy

    代理模式是设计模式中的一种结构型模式,它在对象交互中起到了中介的作用,允许通过代理对象来控制对原对象的访问。代理模式的核心思想是为一个对象提供一个替身,以便增加新的功能或者控制对原对象的访问。这种模式...

    研磨设计模式(完整带书签).part1.pdf

    第11章 代理模式(Proxy) 第12章 观察者模式(Observer) 第13章 命令模式(Command) 第14章 迭代器模式(Iterator) 第15章 组合模式(Composite) 第16章 模板方法模式(Template Method) 第17章 策略模式...

    结构型模式之代理模式(Proxy)

    代理模式是一种设计模式,属于结构型模式之一,其主要目的是为其他对象提供一个代理,以控制对该对象的访问。在实际应用中,代理模式能够帮助我们实现如下的功能: 1. 远程代理:代理对象可以代表一个位于远程系统...

Global site tag (gtag.js) - Google Analytics