11.2 解 决 方 案
11.2.1 使用代理模式来解决问题
用来解决上述问题的一个合理的解决方案就是代理模式。那么什么是代理模式呢?
1.代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。
2.应用代理模式来解决问题的思路
仔细分析上面的问题,一次性访问多条数据,这个可能性是很难避免的,是客户的需要。也就是说,要想节省内存,就不能从减少数据条数入手了,那就只能从减少每条数据的数据量上来考虑。
提示
一个基本的思路如下:由于客户访问多条用户数据的时候,基本上只需要看到用户的姓名,因此可以考虑刚开始从数据库查询返回的用户数据就只有用户编号和用户姓名,当客户想要详细查看某个用户数据的时候,再次根据用户编号到数据库中获取完整的用户数据。这样一来,就可以在满足客户功能的前提下,大大减少对内存的消耗,只是每次需要重新查询一下数据库,算是一个以时间换空间的策略。
可是该如何来表示这个只有用户编号和姓名的对象呢?它还需要实现在必要的时候访问数据库去重新获取完整的用户数据。
代理模式引入一个Proxy对象来解决这个问题。刚开始只有用户编号和姓名的时候,不是一个完整的用户对象,而是一个代理对象。当需要访问完整的用户数据的时候,代理会从数据库中重新获取相应的数据,通常情况下是当客户需要访问除了用户编号和姓名之外的数据的时候,代理才会重新去获取数据。
11.2.2 代理模式的结构和说明
代理模式的结构如图11.1所示。
图11.1 代理模式的结构示意图
■ Proxy:代理对象,通常具有如下功能。
实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象。
可以控制对具体目标对象的访问,并可以负责创建和删除它。
■ Subject:目标接口,定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。
■ RealSubject:具体的目标对象,真正实现目标接口要求的功能。
在运行时刻一种可能的代理结构的对象图如图11.2所示。
11.2.3 代理模式示例代码
(1)先看看目标接口的定义。示例代码如下:
(2)接下来看看具体目标对象的实现示意。示例代码如下:
(3)再来看看代理对象的实现示意。示例代码如下:
11.2.4 使用代理模式重写示例
要使用代理模式来重写示例,首先就需要为用户对象定义一个接口,然后实现相应的用户对象的代理。这样在使用用户对象的地方,使用这个代理对象就可以了。
这个代理对象,在起初创建的时候,只需要装载用户编号和姓名这两个基本的数据,然后在客户需要访问除这两个属性外的数据的时候,才再次从数据库中查询并装载数据,从而达到节省内存的目的。因为如果用户不去访问详细的数据,那么这些数据就不需要被装载,对内存的消耗就会减少。
先看看这个时候系统的整体结构,如图113所示。
此时的UserManager类充当了标准代理模式中的Client的角色,因为是它在使用代理对象和用户数据对象的接口。
还是看看具体的代码示例,会更清楚。
(1)先看看新定义的用户数据对象的接口,非常简单,就是对用户数据对象属性操作的getter/setter方法,因此也没有必要去注释了。示例代码如下:
图11.3 代理模式重写示例的系统结构示意图
(2)定义了接口,需要让UserModel来实现它。基本没有什么变化,只是要实现这个新的接口而已,就不再代码示例了。
(3)接下来看看新加入的代理对象的实现。示例代码如下:
(4)看看此时UserManager的变化,大致如下。
■ 从数据库查询值的时候,不需要全部获取了,只需要查询用户编号和姓名的数据就可以了。
■ 把数据库中获取的值转变成对象的时候,创建的对象不再是UserModel,而是代理对象,而且设置值的时候,也不是全部都设置,只是设置用户编号和姓名两个属性的值。
示例代码如下:
(5)写个客户端来测试看看,是否能正确实现代理的功能!示例代码如下:
运行结果如下:
仔细查看上面的结果数据会发现,如果只是访问用户编号和用户姓名的数据,是不需要重新查询数据库的。只有当访问到这两个数据以外的数据时,才需要重新查询数据库以获得完整的数据。这样一来,如果客户不访问除这两个数据以外的数据,那么就不需要重新查询数据库,也就不需要装载那么多数据,从而节省了内存。
(6)1+N次查询。
看完上面的示例,可能有些朋友会发现,这种实现方式有一个潜在的问题,就是如果客户对每条用户数据都要求查看详细数据的话,那么总的查询数据库的次数会是1+N次之多。
第一次查询,获取到N条数据的用户编号和姓名,然后展示给客户看。如果这个时候,客户对每条数据都点击查看详细信息的话,那么每一条数据都需要重新查询数据库,那么最后总的查询数据库的次数就是1+N次了。
从上面的分析可以看出,这种做法最合适的场景就是:客户大多数情况下只需要查看用户编号和姓名,而少量的数据需要查看详细数据。这样既节省了内存,又减少了操作数据库的次数。
延伸
看到这里,可能会有朋友想起,Hibernate这类ORM的框架,在Lazy Load的情况下,也存在1+N次查询的情况,原因就在于,Hibernate的Lazy Load就是使用代理来实现的,具体的实现细节这里就不去讨论了,但是原理是一样的。