--“design with the think of sharing objects”,Flyweight模式应用
“共享”的思想
共享的idea是生活中最基本的idea,不必有意的使用,到处已经存在了。在生活中,大部分事物都是被多人多次使用的,这都是共享的实际应用。
之于OO的共享
OO中的共享,无非就是说让“对象”也能被“多人多次”(这里的“人”也无非就是进程、线程而已)使用,更详细的说,就是要让对象的生存空间更大一些,生存周期更长一些。
自己闷个儿脑子,提炼出了几个需要使用共享的环境(context),也可以说是原因吧:
1. 为了保持“对象”的一致,我们需要共享。例如,“国家主席”就一个,不能多了,如果多了,难免决策混乱。
2. 为了控制“对象”的存储空间,我们需要共享。毕竟,目前来说,系统的memory还是编程时最珍贵的资源。
3. 为了优化“对象”的创建消耗,我们需要共享。如果,一个对象的创建过程消耗太大,系统不能支持频繁的创建,共享的使用它也是一个好主意。
4. 等等。
而在实际的应用中,往往我并没有细想“我为什么使用共享?”,已经不自觉的就用了;如果真的认真分析起来,基于的环境也是多样,并不会只是上面的其中一种。
常用的“共享”方法或模式(我曾经用过的,知道的不多,望谅解):
1. “Singleton模式”:一个class就一个对象实例,大家都用它,满足context1。
2. “pool技术”:只提供一定数目的对象,大家都用他们,实现context2、context3。
3. “flyweight模式”:一个class的一个状态就一个对象实例,实现一个状态对象的共享,实现context2、context3。
使用时要注意的地方:
1. 确定共享的scope。例如,在Java Web Application中就是选择是page,session还是application,当然也可以是jvm级别的static。
2. 确认thread safe。当共享的对象可能被多个线程共享时,这是不可以回避的问题。
3. 应对对象状态的变化。一旦共享的对象发生了变化,我们怎么处理?改变之,舍弃之?也是我们需要确定的。
项目中的应用:
项目需求:
为学校的同学提供Web查询,查询的内容有很多。其中,“查课表”、“查考表”是最为关键的需求,以后可能还要提供“查询空闲自习教室”的功能。
在这些查询中,有一个共同点,就是都涉及“教室”这一对象。“查课表”时要告诉同学在哪个教室上课,“查考表”时要告诉同学在哪个教室考试,等等。
数据库设计:
对于“查课表”用例,有关的数据库设计如下:
对象层的设计:
学生每查询一门课程的课表,系统就会sql查询“视图V_LESSONSCHEDULE”,进而生成一个LessonSchedule对象,然后返回给用户显示。当然,在生成这个LessonSchedule对象的过程中,属于它的Classroom对象,以及更深一步的Building、Area对象都会生成。下面就是这个过程的顺序图:
因此,每生成一个“课表”对象(LessonSchedule)或“考表”对象(ExamSchedule)时,都要:
1. 查数据库中的教室、教学楼、校区的信息;
2. 创建相应的“教室对象”(包括了属于它的“教学楼”对象和“校区”对象)。
考虑共享“教室”对象
“教室”对象一旦可以生成以后,完全可以给后续共享使用,不必要每个查询都要去生成。
详细说是基于下面的考虑:
1. 这类查询用例(查课表,查考表)发生的频繁很高很高,也就是说,一旦让用户查起来,系统中会产生大量的“教室”对象这类对象,应该说会占很大的内存空间。
2. 共享“教室”对象后,可以减少对数据库的查询次数,并降低了查询粒度(以前是基于二级视图查询,现在可以基于基本表查询),提高了一点数据库查询性能。
当然,同时我脑袋中也有反对的声音:
1. 虽说,这类查询会产生很多相同的“教室”对象,但是JVM本生提供的垃圾回收功能完全可以处理它。除非,“同时”有很多很多这类对象都在被使用,根本回收不了,才会造成内存短缺。
2. 如果,我以某种共享机制让这些“教室”对象,在系统中存在下来(延长了生命周期)了。而它们本身的数目就很多(如,我们学校就有××),可能还没有等你用上,系统已经挂了。另外,如果不是同时有很多查询需要,我留这么多“教室”对象在系统里,反而是占了内存,而不是优化了内存的使用。
1. 所有模式的通病――“增加了复杂度”。
结论:
经过我们分析,系统对于“教室”对象的重复使用,频繁程度非常高。一般,有10个人同时使用,就有5个人左右涉及的用例要使用“教室”对象。把它共享起来,还是有一定必要的。
进一步考虑:
而实际上,我们可以进一步共享下去:
除了让客户端共享“教室对象(Classroom)”外,还可以让“教室对象”共享“教学楼对象(Building)”,和让“教学楼对象”共享“校区对象(Area)”。
因此,最终的共享是在三级上都实现。
Flyweight模式:
特点:
书上说:“享元模式可以使系统中的大量小粒度对象被共享使用”。第一,对象出现的量要大,想必这比较好理解,很少使用的也就没有必要共享了;第二,要小粒度,我比较纳闷?难道对于大粒度对象就不行吗?可能书上认为,大粒度对象的共享已经占了比较大的空间,没有小对象那么有效吧。
另外,书上还说,要使用“享元模式”,被共享的对象的状态(类别)要比较固定,这样就可以为每一个状态仅仅创建一个对象。当然,如果每次使用对象时,对象的状态都是不一样的,那就根本不存在共享这些对象的必要了。
联系项目思考:
基于上面对项目的分析,“教室”、“教学楼”、“校区”对象都是在系统中会被大量使用的对象,而且粒度的确比较小;并且它们有固定的类别,而且不易改变。如校区对象,暂时就有4个。教学楼可能40-50个左右。很适合“享元模式”的使用环境。
确定共享方式:
1. 确定共享对象的scope。在本web程序中,这些共享对象的scope理应是application,而更简单的一个作法就是把这些对象设为static,我选择后者。
2. 确认thread safe。这些对象是可能被多个servlet访问的,也就是有可能存在多线程访问。但是,由于这些对象的可变性很差,一旦创建就不大可能变化。因此,我决定把这写共享对象设计成不变模式的,一旦创建就只会被读取,而不会改写,这样就不存在多线程控制的问题了。
3. 应对对象状态的变化,如某个教室的类型变了。这里采取的是舍弃的方法,为每个工厂添加了一个清空方法――clear(),用于清空已经生成的共享对象。
设计类图:
当然,也可以把这些工厂都设计成“Singleton模式”的,使它们只会有一个实例。
客户端使用:
由于共享的对象都被包含在了“课表”和“考表”对象里,不会被客户端直接访问,因而不会对客户端的使用有任何影响:
实例代码
2
3 LessonSchedule oneLesson = LessonSchedule.findByLessonNum( 32 );
4
5
6
7 // 获得教室对象
8
9 Classroom oneClassroom = oneLesson.getLnkClassroom();
10
11
12
13 // 获得教学楼对象
14
15 Building oneBuilding = oneClassroom.getLnkBuilding();
16
17
18
19 // 获得校区对象
20
21 Area oneArea = oneBuilding. getLnkArea();
22
23
24
25 // 再次重新生成一个编号为32号的“课表对象”
26
27 LessonSchedule twoLesson = LessonSchedule.findByLessonNum( 32 );
28
29
30
31 // 获得教室对象
32
33 Classroom twoClassroom = twoLesson.getLnkClassroom();
34
35
36
37 // 获得教学楼对象
38
39 Building twoBuilding = twoClassroom.getLnkBuilding();
40
41
42
43 // 获得校区对象
44
45 Area twoArea = twoBuilding. getLnkArea();
46
oneClassroom与twoClassroom;oneBuilding与twoBuilding;oneArea与twoArea由于都是32号课程的东西,根据我们的设计意图,应该实现共享。
而实际上,它们每对的确是同一个对象的引用。因此,实现了预期的设想。
Review:
在本项目中,当第一次设计出来的时候,我们发现了某些对象恰好有共享的需要。
而更多的实际情况是,这些需要共享的“信息或状态”在设计中并不是那么恰好的表现为“一个对象”的粒度,而是要不就包含在一个对象内部,要不就跨几个对象。在这样的情况下,共享的设计更多是发生在代码重构阶段而不是第一的设计阶段。当然,为了共享对象而做出的代码重构,最重要的一步就是把需要共享的“信息或状态”设计成为新的对象。
对于,“享元模式”来说,就是要把需要共享的“信息或状态”设计成“享元对象”。别的在此就不说了,因为我也不懂了,呵呵。
MARCO ZHANG 2006年2月23日13:48:49