Java Web Application开发日志之五--“design with the think of sharing objects”,Flyweight模式应用

Java Web Application开发日志之五
--“design with the think of sharing objects”,Flyweight模式应用


“共享”的思想

共享的idea是生活中最基本的idea,不必有意的使用,到处已经存在了。在生活中,大部分事物都是被多人多次使用的,这都是共享的实际应用。

 

之于OO的共享

 

OO中的共享,无非就是说让“对象”也能被“多人多次”(这里的“人”也无非就是进程、线程而已)使用,更详细的说,就是要让对象的生存空间更大一些,生存周期更长一些。

 

自己闷个儿脑子,提炼出了几个需要使用共享的环境(context),也可以说是原因吧:

1.         为了保持“对象”的一致,我们需要共享。例如,“国家主席”就一个,不能多了,如果多了,难免决策混乱。

2.         为了控制“对象”的存储空间,我们需要共享。毕竟,目前来说,系统的memory还是编程时最珍贵的资源。

3.         为了优化“对象”的创建消耗,我们需要共享。如果,一个对象的创建过程消耗太大,系统不能支持频繁的创建,共享的使用它也是一个好主意。

4.         等等。

 

而在实际的应用中,往往我并没有细想“我为什么使用共享?”,已经不自觉的就用了;如果真的认真分析起来,基于的环境也是多样,并不会只是上面的其中一种。

 

常用的“共享”方法或模式(我曾经用过的,知道的不多,望谅解):

1.         Singleton模式”:一个class就一个对象实例,大家都用它,满足context1

2.         pool技术”:只提供一定数目的对象,大家都用他们,实现context2context3

3.         flyweight模式”:一个class的一个状态就一个对象实例,实现一个状态对象的共享,实现context2context3

 

使用时要注意的地方:

1.         确定共享的scope。例如,在Java Web Application中就是选择是pagesession还是application,当然也可以是jvm级别的static

2.         确认thread safe。当共享的对象可能被多个线程共享时,这是不可以回避的问题。

3.         应对对象状态的变化。一旦共享的对象发生了变化,我们怎么处理?改变之,舍弃之?也是我们需要确定的。

 

项目中的应用:

 

项目需求:

为学校的同学提供Web查询,查询的内容有很多。其中,“查课表”、“查考表”是最为关键的需求,以后可能还要提供“查询空闲自习教室”的功能。

在这些查询中,有一个共同点,就是都涉及“教室”这一对象。“查课表”时要告诉同学在哪个教室上课,“查考表”时要告诉同学在哪个教室考试,等等。

 

数据库设计:

对于“查课表”用例,有关的数据库设计如下:
o_5-1-1.jpg 


对象层的设计:

o_5-1.JPG
 

学生每查询一门课程的课表,系统就会sql查询“视图V_LESSONSCHEDULE”,进而生成一个LessonSchedule对象,然后返回给用户显示。当然,在生成这个LessonSchedule对象的过程中,属于它的Classroom对象,以及更深一步的BuildingArea对象都会生成。下面就是这个过程的顺序图:

 

o_5-2.JPG 


因此,每生成一个“课表”对象(
LessonSchedule)或“考表”对象(ExamSchedule)时,都要:

1.         查数据库中的教室、教学楼、校区的信息;

2.         创建相应的“教室对象”(包括了属于它的“教学楼”对象和“校区”对象)。

 

考虑共享“教室”对象

“教室”对象一旦可以生成以后,完全可以给后续共享使用,不必要每个查询都要去生成。

 

详细说是基于下面的考虑:

1.         这类查询用例(查课表,查考表)发生的频繁很高很高,也就是说,一旦让用户查起来,系统中会产生大量的“教室”对象这类对象,应该说会占很大的内存空间。

2.         共享“教室”对象后,可以减少对数据库的查询次数,并降低了查询粒度(以前是基于二级视图查询,现在可以基于基本表查询),提高了一点数据库查询性能。

 

当然,同时我脑袋中也有反对的声音:

1.         虽说,这类查询会产生很多相同的“教室”对象,但是JVM本生提供的垃圾回收功能完全可以处理它。除非,“同时”有很多很多这类对象都在被使用,根本回收不了,才会造成内存短缺。

2.         如果,我以某种共享机制让这些“教室”对象,在系统中存在下来(延长了生命周期)了。而它们本身的数目就很多(如,我们学校就有××),可能还没有等你用上,系统已经挂了。另外,如果不是同时有很多查询需要,我留这么多“教室”对象在系统里,反而是占了内存,而不是优化了内存的使用。

1.         所有模式的通病――“增加了复杂度”。

 

结论:

经过我们分析,系统对于“教室”对象的重复使用,频繁程度非常高。一般,有10个人同时使用,就有5个人左右涉及的用例要使用“教室”对象。把它共享起来,还是有一定必要的。

 

进一步考虑:

而实际上,我们可以进一步共享下去:

除了让客户端共享“教室对象(Classroom)”外,还可以让“教室对象”共享“教学楼对象(Building)”,和让“教学楼对象”共享“校区对象(Area)”。

因此,最终的共享是在三级上都实现。

 

Flyweight模式:

 

特点:

书上说:“享元模式可以使系统中的大量小粒度对象被共享使用”。第一,对象出现的量要大,想必这比较好理解,很少使用的也就没有必要共享了;第二,要小粒度,我比较纳闷?难道对于大粒度对象就不行吗?可能书上认为,大粒度对象的共享已经占了比较大的空间,没有小对象那么有效吧。

 

另外,书上还说,要使用“享元模式”,被共享的对象的状态(类别)要比较固定,这样就可以为每一个状态仅仅创建一个对象。当然,如果每次使用对象时,对象的状态都是不一样的,那就根本不存在共享这些对象的必要了。

 

联系项目思考:

基于上面对项目的分析,“教室”、“教学楼”、“校区”对象都是在系统中会被大量使用的对象,而且粒度的确比较小;并且它们有固定的类别,而且不易改变。如校区对象,暂时就有4个。教学楼可能4050个左右。很适合“享元模式”的使用环境。

 

确定共享方式:

1.         确定共享对象的scope。在本web程序中,这些共享对象的scope理应是application,而更简单的一个作法就是把这些对象设为static,我选择后者。

2.         确认thread safe。这些对象是可能被多个servlet访问的,也就是有可能存在多线程访问。但是,由于这些对象的可变性很差,一旦创建就不大可能变化。因此,我决定把这写共享对象设计成不变模式的,一旦创建就只会被读取,而不会改写,这样就不存在多线程控制的问题了。

3.         应对对象状态的变化,如某个教室的类型变了。这里采取的是舍弃的方法,为每个工厂添加了一个清空方法――clear(),用于清空已经生成的共享对象。

 

设计类图:

o_5-3.JPG 

当然,也可以把这些工厂都设计成“Singleton模式”的,使它们只会有一个实例。

 

客户端使用:

由于共享的对象都被包含在了“课表”和“考表”对象里,不会被客户端直接访问,因而不会对客户端的使用有任何影响:

 

实例代码

 
 1 // 取得编号为32号课程的“课表对象”
 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

oneClassroomtwoClassroomoneBuildingtwoBuildingoneAreatwoArea由于都是32号课程的东西,根据我们的设计意图,应该实现共享。

而实际上,它们每对的确是同一个对象的引用。因此,实现了预期的设想。

 

Review

在本项目中,当第一次设计出来的时候,我们发现了某些对象恰好有共享的需要。

 

而更多的实际情况是,这些需要共享的“信息或状态”在设计中并不是那么恰好的表现为“一个对象”的粒度,而是要不就包含在一个对象内部,要不就跨几个对象。在这样的情况下,共享的设计更多是发生在代码重构阶段而不是第一的设计阶段。当然,为了共享对象而做出的代码重构,最重要的一步就是把需要共享的“信息或状态”设计成为新的对象。

 

对于,“享元模式”来说,就是要把需要共享的“信息或状态”设计成“享元对象”。别的在此就不说了,因为我也不懂了,呵呵。


                                          MARCO ZHANG 2006年2月23日13:48:49

你可能感兴趣的:(Java Web Application开发日志之五--“design with the think of sharing objects”,Flyweight模式应用)