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()){ //只需要重新获取除了userId和name外的数据 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就是使用代理来实现的,具体的实现细节这里就不去讨论了,但是原理是一样的。
相关推荐
介绍在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界
代理模式是一种设计模式,它在软件工程中扮演着重要的角色,允许我们为其他对象提供一个替代接口,以控制对原始对象的访问。这种模式的主要目的是为了增加灵活性、安全性或者在不修改原有对象的情况下,增强或扩展其...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要的角色,特别是在iOS平台的应用程序设计中。代理模式的核心思想是为一个对象提供一个替身或代理,以控制对这个对象的访问。这种模式允许我们通过代理来间接...
代理模式是设计模式的一种,它提供了一种对目标对象进行增强或者控制访问的方式。在本实例中,我们将深入探讨Java中的代理模式及其应用。 代理模式的核心思想是为一个对象创建一个代理对象,这个代理对象在客户端和...
代理模式是设计模式中的一种结构型模式,它在对象交互中起到了中介的作用,允许通过代理对象来控制对原对象的访问。代理模式的核心思想是为一个对象提供一个替身,以便增加新的功能或者控制对原对象的访问。这种模式...
**设计模式之代理模式(Proxy Pattern)** 设计模式是软件工程中的一种最佳实践,它是在特定情境下解决常见问题的模板。代理模式是其中一种行为设计模式,它的核心思想是为一个对象提供一个替身或者代理,以控制对...
**设计模式实现——代理模式** 在软件工程中,设计模式是一种通用可重用的解决方案,它描述了在特定上下文中经常出现的问题以及该问题的解决方案。代理模式是设计模式的一种,它提供了一种对目标对象的间接访问方式...
在这个“Java设计模式-代理模式例子”中,我们将深入探讨代理模式的概念、实现方式以及它在实际开发中的应用。 代理模式的核心思想是为一个对象提供一个替身,这个替身即代理对象,代理对象控制对原对象的访问。在...
**Java设计模式——代理模式详解** 代理模式是软件设计模式中的一个重要组成部分,它在Java编程中扮演着举足轻重的角色。代理模式的核心思想是为一个对象提供一个替身,这个替身即代理对象,代理对象可以控制对原...
代理模式是设计模式的一种,它的主要目的是在不改变原有对象的基础上,为一个对象提供额外的功能或者控制对这个对象的访问。在Android开发中,代理模式的应用尤为常见,尤其在处理复杂的业务逻辑、网络请求、界面...
代理模式是一种设计模式,属于结构型模式之一,其主要目的是为其他对象提供一个代理,以控制对该对象的访问。在实际应用中,代理模式能够帮助我们实现如下的功能: 1. 远程代理:代理对象可以代表一个位于远程系统...
SignalR提供了两种主要的工作模式:代理模式和非代理模式。这两种模式在实现上有所不同,各自具有优缺点,适用于不同的场景。 **1. 代理模式(Proxy Mode)** 在代理模式下,SignalR为每个Hub(服务端的业务逻辑...
代理模式是一种常用的设计模式,它在软件开发中起到了中介或者代表的作用。代理模式的主要目的是为其他对象提供一种代理以控制对这个对象的访问。通过引入代理,我们可以增加新的功能,如缓存、日志记录、访问控制等...
### Java代理模式与Java动态代理详解 #### 一、代理模式概述 代理模式是一种软件设计模式,它在客户端和目标对象之间提供了一种间接层。这种模式的主要目的是控制客户端对目标对象的访问,并且可以在不修改原有...
代理模式在软件设计中是一种常用的设计模式,尤其在Android开发中,它可以帮助我们实现复杂的控制逻辑,隔离复杂性,以及提供额外的功能。在Android上下文中,代理模式常常用于数据加载、权限控制、事件处理等方面。...
代理模式(Proxy) 定义: 为其他对象提供一种代理以控制对这个对象的访问 结构: 由三部分组成 1.RealSubject(真实对象): 真正会调用到的对象 2.Proxy(代理对象): 代理真实对象的地方 3.Subject(共同点): 代理对象...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要角色,允许我们通过一个代理类来控制对原对象的访问。在《设计模式:可复用面向对象软件的基础》(通常称为GoF设计模式)中,代理模式被定义为“为其他对象...
在Java编程中,代理模式是一种常用的面向对象设计模式,它允许我们为一个对象提供一个代理以控制对该对象的访问。代理模式通常用于增加额外的功能,如日志、权限检查等,或者为了创建虚拟代理以提高性能。以下是Java...