我的隐式生活(My Implicit Life)

继续搞“对象”,玩OO.

首页 新随笔 联系 聚合 管理
  11 Posts :: 1 Stories :: 39 Comments :: 0 Trackbacks

2006年2月16日 #

近期写了个电子书的C/S模式的下载工具,一个server端,一个client端。

目的就是想在公司能很方便的访问家里那些收集很久电子书,方便查阅。

用了1,2个星期,虽然写的很烂,但是没有用任何第三方的产品(server or db)。

现在里面的书籍已经接近200本了。

注:server就用了家里的adsl,所以速度慢,关闭不定时。毕竟玩玩嘛。

有兴趣的朋友先装个jdk1.5。再运行下面压缩包里的exe文件执行即可。

点此下载

User ID:               blogjava
Password:             blogjava
 

posted @ 2006-10-15 13:21 marco 阅读(3460) | 评论 (9)编辑 收藏

Java Collection Framwork中的类的确是最重要的基础api,实现任何算法,基本上都很难离开它。

因此理解这堆“集合(Collection)类”很有必要。声明一下,以前一直都是叫它们集合类,但是好像Think In Java的作者鄙视了这个说法,严格的说应该叫Container类,而后看了它整整一章书以后,觉得还是人家说的有道理。

它说这个container类库,包含了两大类,Collection和Map,而Collection又可以分为List和Set。当然这些抽象概念都被定义成了接口。

话说,这样的分类的确是严格按照类之间的继承关系来说得,但是俺总觉得很别扭,真动手的时候,还是很难选择。当然,Anytime and Anywhere使用ArrayList绝对都能解决问题,但这样做毕竟太农民了一点。

所以,我自己有了一些想法。先回归到最基本最基本的数据结构的层面,管你是Collection还是Container,反正描述的都是一堆东西吧。数据结构第一章讲了一个结构:在物理上连续分配空间的顺序结构,叫顺序表(希望记性是好的),而离散分配空间的,应该叫做链表,最常用的就是单链表。这两个东西,其实就是很多复杂数据结构的基础,还记得吗,当时就是讲完这些东西,才开始讲栈、队列、二叉树、有向无向图的。所以,这个顺序结构是很基础的。而在JAVA中,顺序表对应的就是List接口,而一般顺序表就是ArrayList(有效进行随机index查找);而单链表就是LinkedList(有效进行插入和删除),两个的优劣当年都讲烂了,这里就不说了。

有了这两个结构以后,JAVA就不提供Stack和Queue单独的类了,因为,用户可以用上面两个类轻易的去实现。

那Set和Map有怎么跟List连上关系呢?

我认为可以把它们看成是无序和单一的List(Map只是两个有映射关系的List罢了)。

Set和Map无序和单一的特性,决定了它们天大的需求就是根据关键字(元素对象)检索。so,为了效率,必须hash。

有了HashSet和HashMap。

同时,如果非要保持住元素的顺序,有了LinkedHashSet、LinkedHashMap。


结论:

假如你的需求是
1:往Container中放的对象是无序且单一的;
2:经常要检索。
用HashSet或HashMap吧。

ps:这两个条件其实是一回事,因为如果是不单一的话,你去检索它干嘛。

如果进而需要保持元素的顺序,不要让他顺便iteration,那就选择LinkedHashSet和LinkedHashMap。

假如你的需求不满足以上1&2,那你放心,List肯定能帮你解决,你只要稍微想一下是ArrayList好还是LinkedList好。

题外话:

关于Hash,务必记得要让自己的元素对象override hashCode()和 equles() 方法,要不你直接可以洗了睡。

关于所有这些Container,务必记得有个辅助类叫Interator,遍历尽量要用它。

关于一些老的Stack、Vector、HashTable,听说以后不要用了哦。收到啦!!

posted @ 2006-09-20 16:53 marco 阅读(2313) | 评论 (0)编辑 收藏

任何信息,基本都是以文字的形式传播和记录下来的。

在计算机中,文字就是字符的集合,也就是字符串,C就是因为对字符串设计的不好,才那么容易溢出。而别的一些高级语言,对于这个进行了很多的改进。

编程的人由于技术方向和应用方向的不同,日常编程的内容差距很大。但是对于字符串的处理,那可是永远都避不开的工作。

昨天跑步的时候,想了一下,对于字符串的操作有那么多(search,match,split,replace),感觉很烦杂,能不能抓住这些操作的一个基本集?

不知道对不对,反正想出来了一个,这个基本操作就是search,这里的search的意思是:在输入串中找到目标串的开始位置(start index),和结束位置(end index)。

有了这个基本集,别的操作都很好衍生出来:

局部match:其实就是要求search操作至少返回一个start index。

全match:其实要求search操作的至少返回一个start index,并且start index要为零,end index要为输入串的全长。

split:其实就是search操作之后,把前一个end index和当前的start index之间的字符串截出来而已。

replace:其实就是search操作之后,把start index和end index之间的字符串换成另外的而已。

所以,归根到底,都是一个search操作的拓展罢了。这么一想,感觉清晰多了。

这么一来,API对search的能力支持的好坏和效率高低是衡量字符串操作功能的标准,当然,如果有直接支持match,split,replace操作的话就更好了。

java对字符串search的支持,最基本的就是下面的String的indexOf方法:

int indexOf(String str)
          Returns the index within this string of the first occurrence of the specified substring.

这里我想说的是,很多时候我们所谓要search的目标串,根本就不是固定单一的,而是变化多样的。如果只有一两种情况,最多用两次上面的方法呗。但是有些情况是近乎不可能罗列的,例如,我们讲的代表email的字符串,我们不可能遍历它吧。

所以,需要一种能够通用表达字符串格式的语言。这就是Regular Expression(re)。

假如上面方法indexOf的str参数能支持re做为参数的话,那对于这种多样的search也可以用上面的方法了。

可惜,indexOf不支持re作为参数。

so,以下就介绍java api中可以用re作为参数的字符串操作方法(参数中的regex就是re)。

--------------------->>
String类的:

全match操作:
boolean matches(String regex)
          Tells whether or not this string matches the given regular expression.

全replace操作:
String replaceAll(String regex, String replacement)
          Replaces each substring of this string that matches the given regular expression with the given replacement.

首个replace操作:
String replaceFirst(String regex, String replacement)
          Replaces the first substring of this string that matches the given regular expression with the given replacement.

全split操作:
String[] split(String regex)
          Splits this string around matches of the given regular expression.


有限制数的split操作:
String[] split(String regex, int limit)
          Splits this string around matches of the given regular expression.

<<---------------------

可惜啊,可惜,可惜java的String类里面没有可以支持re的search方法,那如果要用re来search,只好使用java中专门的re类库。

java中的re类库主要就两个类,一个叫Pattern,顾名思义,代表re的类。一个叫Matcher类,反映当前match状况的类(如存放了当前search到的位置,匹配的字符串等等信息)。

一般在构造中,“re的表达式”作为参数传递入Pattern类,“输入串(待过滤串)”作为参数传递入Matcher类。

然后使用Matcher类的字符串search方法就可以了。Matcher真正提供search功能的API叫find。下面列出。
--------------------->>
Matcher类search操作相关的方法:

boolean lookingAt()
          Attempts to match the input sequence, starting at the beginning, against the pattern.

boolean matches()
          Attempts to match the entire input sequence against the pattern.

boolean find()
          Attempts to find the next subsequence of the input sequence that matches the pattern.

String group()
          Returns the input subsequence matched by the previous match.

<<---------------------

前三个都是search方法,返回成功与否。第四个是返回当前search上的字符串。

ok,至此。使用re的search操作也有眉目了。

当然,Pattern和Matcher也包含直接使用re进行的match,split,replace操作。

--------------------->>
Patter类别的字符串操作方法

全match操作:
static boolean matches(String regex, CharSequence input)
          Compiles the given regular expression and attempts to match the given input against it.

全split操作:
String[] split(CharSequence input)
          Splits the given input sequence around matches of this pattern.

有限制数的split操作:
String[] split(CharSequence input, int limit)
          Splits the given input sequence around matches of this pattern.


Matcher类别的字符串操作方法

全replace操作:
String replaceAll(String replacement)
          Replaces every subsequence of the input sequence that matches the pattern with the given replacement string.

首个replace操作:
String replaceFirst(String replacement)
          Replaces the first subsequence of the input sequence that matches the pattern with the given replacement string.

动态replace(replacement可以根据被替代的字符串变化而变化)
Matcher appendReplacement(StringBuffer sb, String replacement)
          Implements a non-terminal append-and-replace step.

StringBuffer appendTail(StringBuffer sb)
          Implements a terminal append-and-replace step.

<<---------------------

总结:
当必须使用re的时候,search操作就要用到Pattern,Matcher,当然动态的replace操作也要用到这两个类。而别的match,replace,split操作,可以使用pattern,Matcher,当然也可以直接使用String,推荐还是用回咱们的String吧。

注:以上都是看jdk1.4以上的文档得出的结论,以前版本不能用不负责任。

posted @ 2006-08-31 15:13 marco 阅读(2683) | 评论 (0)编辑 收藏

创建和销毁对象

重点关注对象的创建和销毁:什么时候、如何创建对象,什么时候、什么条件下应该避免创建对象,如何保证对象在合适的方式下被销毁,如何在销毁对象之前操作一些必须的清理行为。

尝试用静态工厂方法代替构造器

如果一个 client 要实例化一个对象来使用,傻 b 都知道应该先调用类的构造器来 new 一个对象,之后再调用相应的方法。除了这个方式, Java Effective 还建议了另一种方法:用静态工厂方法来提供一个类的实例。以下的例子不反映两者的优劣,只是反映两者在代码实现上的不同,优劣之后再谈:

假设咱们要一个颜色为黑色、长度为 50cm 的锤子,自然就用构造器创建一个

Hammer myHammer =  new Hammer(Color.BLACK, 50);

而用静态工厂方法来实例化一个对象,如下

Hammer myHammer = Hammer.factory(Color.BLACK,50);

也可以用专门的一个工厂类来实例化

Hammer myHammer = Toolkit.factory(“Hammer”, Color.BLACK,50);  

单纯从上面的代码上看,真的只有傻 b 才会选择静态工厂的方法,完全就是多此一举,直接 new 又快又爽,搞这么麻烦做莫斯(武汉话“什么”的意思)?

别急,别急,你急个莫 b (武汉粗话:基本就是“你急个毛”的意思)?

下面就说说用静态工厂代替构造器的好处( advantage )和不好处( disadvantage )。

第一个好处,讲你都不信,行家们认为,构造器有一个不好的地方就是:这个方法的签名( signture )太固定了。

构造器的名字是固定的,生个 Hammer ,构造器的名字就是 Hammer (……),唯一能变化的地方就是参数,假设我的这个锤子有两个很变态的构造需要:

1 :第一个参数是颜色( Color 型),第二个参数是锤子头的重量( int 型)。

Hammer Color c, int kg {

//remainder omited

}

2 :第一个参数是颜色( Color 型),第二个参数是锤子的长度( int 型)。

Hammer Color c, int cm {

//remainder omited

}

感觉满足需要了,但是细心一看,完了,构造器的参数列表类型重复了,肯定编译通不过,这是面向对象构造器天生的缺陷——唯一的变化就是参数,参数都分辨不了,就真的分辨不了。

而另外就算参数能分辨的了,构造器一多,它的参数一多,您根本就不知道每个参数是用来干什么的,只能去查阅文档,在您已经眼花缭乱的时候再去查文档,一个一个的对,折磨人的活。

这个时候,您就可以考虑用静态工厂方法来实例化对象了。因为静态工厂方法有一个最简单的特点就是:他有可以变化的方法名(构造器的名字变不了)。用名字的不同来代表不同的构造需要,这么简单的普通的特点在这里就是它相对于构造器的 advantage

如上面的锤子的例子可以这样:

1 Hammer.produceByWeight (Color c, int kg){

//remainder omited

}

2 Hammer.produceByHeight (Color c, int cm){

//remainder omited

}

这是不是一目了然多了。嗯,我是这样认为的。

第二个好处,“静态工厂方法不需要每次都真的去实例化一个对象”——其实这也是另一些优化方法的前提。

构造器的每次 invoke 必定会产生一个新的对象,而静态工厂方法经过一定的控制,完全可以不用每次 invoke 都生成一个新的对象。

为什么不每次都生成一个对象的原因就不必说了,因为原因太明显。这个原因就是为什么要“共享”对象的原因。

下面讲讲通常使用的两种共享具体策略,也就是具体方法了:

1 :单例模式的需要,一旦需要某个对象有单例的需要,必定对于这类对象的构造只能用静态工厂方法了。

2 flyweight 模式和不变( immutable 模式的需要,这两个模式很多时候都说一起使用的,一旦一些对象我们认为是不变的,那自然就想拿来重用,也就说共享,而 flyweight 就是用来重用这些小粒度对象的。

Boolean.valueOf (boolean) 方法:

Boolean a = Boolean.valueOf (100);

Boolean b = Boolean.valueOf (100);

 a,  b两个引用都是指向同一个对象。

这些对象都是不变的,而 valueOf 的控制就是用的 flyweight 方法。

这种一个状态(如上面一个数字)对应的对象只有一个还有一个好处,就是可以直接通过比较“引用”来判断他们是否 equel (这里的 equel 是逻辑相等的意思),以前需要 a.equels(b) ,而一旦用“ flyweight 模式和不变( immutable 模式”后,避免了产生多余的相同对象,用 a==b 就可以达到 a.equels(b) 的目的了。这样当然优化了 performance   

第三个好处,其实就是工厂方法的核心好处——我把它称为“抽象类型构造器”。它可以为我们提供一个抽象类型的实例,同时必要的隐藏了抽象类型的具体结构。这是 new 怎么都达不到的。

这种模式的好处其实就是面向对象的最核心的好处,抽象和具体可以分离,一旦抽象定义好了,具体的东西可以慢慢的变化,慢慢的拓展——开闭原则。

Collections Framework API ,都是描述集合类型的接口,也就是对于客户端来看,只有 Collection 这个类要认识,而实际上,实现这个接口的 Collection 是多种多样的。如果要让用户都知道这些具体实现的 Collection ,就增加了复杂度。

这时,通过一个静态工厂方法,就可以隐藏各种 Collection 的具体实现,而让 Client 只使用返回的 Collection 对象就可以了。

这里还可以加上一些权限控制,如这些实现只要对于工厂来讲是可以访问的,不用是 public 的,而他们只要通过 public 的工厂就可以提供给用户。非常有利于代码的安全。

静态工厂方法的第一个缺点就是:使用静态工厂方法创建的类的构造器经常都是非公共或非 protected 的。 这样,以后这些类就没有办法被继承了。不过也有人说,不用继承就用 composition 呗。也是!呵呵。

静态工厂方法的第二个缺点是:在 jdk 文档里,这些静态工厂方法很难跟别的静态方法相区别。 而文档中,构造器是很容易看到的。

为了一定程度解决这个问题,我们可以用一些比较特别的名字来给这类静态工厂方法来命名。最常用的有:

valueOf —— 用来放回跟参数“相同值”的对象。

getInstance —— 返回一个对象的实例。单例模式中,就是返回单例对象。

总结:静态工厂方法和构造器都有各自的特点。最好在考虑用构造器之前能先考虑一下静态工厂方法,往往,后者更有用一点。如果权衡了以后也看不出那个好用一些,那就用构造器,毕竟简单本分多了。

posted @ 2006-07-15 12:35 marco 阅读(613) | 评论 (0)编辑 收藏

     摘要: 关键字:Observer Pattern、Java Thread、Java Swing Application 1 近来的阅读 近来寒暑不常,希自珍慰。武汉天气不是狂冷,就是狂热,不时还给我整个雪花,就差冰雹了。   自己做的事吧,也没有什么劲儿。看看自己喜欢的东西,等着希望中的学校能给我offers(是复数),看着自己想去又不想去的公司的未来同事在群里面幻想未来的样子,别操你大...  阅读全文
posted @ 2006-03-14 01:51 marco 阅读(2587) | 评论 (2)编辑 收藏

昨天,以前师兄做的系统因为漏洞又被投诉,头让俺做个presentation给实验室下一级同学总结一下,避免以后再犯错。今天讲完了就放上来,存个证,以后也好翻阅沟通。

不涉及主机安全、网络安全、数据库安全,只从Web应用程序的角度,整理归纳一下面临的主要安全问题。

点击看大图。
r_Web应用程序安全问题.jpg

posted @ 2006-03-09 00:12 marco 阅读(741) | 评论 (5)编辑 收藏

这几天瞄了几本设计模式的书,没有细看具体模式啦,而是老是琢磨那些深奥无比的话。这些话经常出现在计算机的书籍中,很有禅意,也有哲理。听说,高手就喜欢写点这样的话。

还有就是细心体味了一下OO的设计原则,这些原则是凌驾于模式之上的,也就是更宏观的原则。

其中,最高指导的一个就是“开-闭”原则。别的原则,里氏代换原则、依赖倒置原则、组合/聚合复用原则和迪米特法则都是为了达到“开-闭”原则而出现的规则。

这些原则告诉我很多东西,聚焦于一点就是要“面向抽象”来做一切事情。

分析对象的时候,要多分析设计“抽象”的概念,对象之间的联系要多基于抽象的概念而不是具体,这样具体才能能够变化,这样才是开闭。用我自己的话就是要“游走于 抽象”。

这里有一个我必须记住的就是,在封装变化时候,多用聚合/组合,少用继承。在封装原子变化并且是同类型对象时才用继承,别的都尽量用聚合/组合。而且尽量不要用多级继承,多级继承一般意味着有两种变化脉络,可能的话,让两种变化脉络独立演化。很明显,一独立演化,又要聚合/组合了。

还有一个必须记住的是:运用抽象以后,客户端的使用发生了巨大的变化。不再是指那儿用那儿。而是要做更多的准备工作,因为运用抽象,本身就把具体“组合”的职责推迟到使用的阶段。那谁使用,肯定是客户端。所以,客户端的使用要革新。要习惯用工厂,习惯把一系列的抽象定具体了,并按照一定方式“组合”起来用。而且,最终要善于用接口来调用方法。

用小飞推荐的一个工具画了个图,如下:
o_好的OO思想.jpg

                       MARCO ZHANG 2006年2月27日7:18:57

posted @ 2006-02-27 07:40 marco 阅读(853) | 评论 (4)编辑 收藏


“共享”的思想

共享的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
 3LessonSchedule oneLesson = LessonSchedule.findByLessonNum(32);
 4
 5 
 6
 7//获得教室对象
 8
 9Classroom oneClassroom = oneLesson.getLnkClassroom();
10
11 
12
13//获得教学楼对象
14
15Building oneBuilding = oneClassroom.getLnkBuilding();
16
17 
18
19//获得校区对象
20
21Area oneArea = oneBuilding. getLnkArea();
22
23 
24
25//再次重新生成一个编号为32号的“课表对象”
26
27LessonSchedule twoLesson = LessonSchedule.findByLessonNum(32);
28
29 
30
31//获得教室对象
32
33Classroom twoClassroom = twoLesson.getLnkClassroom();
34
35 
36
37//获得教学楼对象
38
39Building twoBuilding = twoClassroom.getLnkBuilding();
40
41 
42
43//获得校区对象
44
45Area twoArea = twoBuilding. getLnkArea();
46

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

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

 

Review

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

 

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

 

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


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

posted @ 2006-02-23 14:14 marco 阅读(1276) | 评论 (4)编辑 收藏

废话:

预料中的日志3暂时生产不出来,话说难产就好,别夭折就行,有点掉价哦,呵呵。

 

因为有些东西还没有想清楚。那就先搞个四吧,这个东西还是清楚那么一点的。

一版描述:

项目需求

使每个servlet能对用户访问权限进行检查。简单来说,就是要给每个servlet加个锁,有钥匙的用户才能访问。

 

而项目中用户所谓的访问权限是基于他拥有的角色。也就是说,servlet对用户访问权限的检查,就是对他所拥有角色的检查。暂时,每个用户只能拥有一个角色。

 

项目的角色很多,但是在web端暂时只有如下的三种:

 

项目暂时角色

游客,学生,教师

 

既然这样,servlet的加锁方式就有:

 

servlet加锁方式

 游客,学生,教师,
游客或学生,游客或教师,学生或教师,
游客或学生或教师

 注:上面的“游客”就是“游客角色有权访问”的意思,依此类推。

 

这里只有关系“或”(||),如果一个servlet的加锁方式是“学生或教师”,也就是说拥有学生或教师角色的用户都可以访问它。关系“与”(&&)在这里不太可能存在,因为没有需求说:某个servlet一定只能由“既是学生又是教师的用户”才能访问,而且前面也说了,暂时一个用户“有且只有”一个角色。

 

So we can get following function


“加锁方式数” = 2的“角色数”次方 - 1


“加锁方式数”是关于“角色数”的指数函数,也就是说它是关于“角色数”成“指数级”增长的,应该说很快了吧。


3
个角色就有23次方-1个,也就是7个加锁方式。

 

运用OO的最基本方式,就是封装对象。既然有为servlet“看门”的责任,那就把这个责任封装成一个对象,用个俗名:validator

 

接着,运用共性和个性的分析方法,既然有那么多种不同的看门方式(加锁方式),那就搞一个接口,然后让各种“不同”都实现这个接口,形成子类。那就有下面的图:

o_4-1.JPG

可以看到,由于有7个加锁方式,那就要有7个子类。每个子类根据自己逻辑override接口的validate方法。

 

这样,对于一个servlet,想让它上什么样的锁,只要让它拿到对应的子类的引用即可,如下图中的ClientServlet,我们规定只能有“学生或教师”才能访问它。它的部分代码便是:


o_4-2.JPG

 

 

1//new对应的Validator接口的子类。
2//这里是学生或教师可访问,因此要new Student_Or_Teacher_Validator
3Validator validator = new Student_Validator();
4//然后调用验证方法就可以了
5boolean ok = validator.validate();

至此,第一个解决方案就出来了。

思考:

不足

validator接口的子类数目随“角色数”成“指数级”增长,数量太多;而且子类中重复逻辑的代码很多,如“Student_Or_Teacher_Validator”就重复了“Student_Validator”和“Teacher_Validator”的逻辑,万一“Student_Validator”的逻辑要改,只要涉及Student的子类都要跟着改,维护上不方便。

 

进一步改进的可能

Student_Or_Teacher_Validator类”的validate方法很大程度上就是“Student_Validator类”的validate方法和“Teacher_Validator类”的validate方法“或操作”出来的结果。

 

因此,能不能考虑由“Student_Validator类的validate方法”和“Teacher_Validatorvalidate方法”动态的构造一个功能如“Student_Or_Teacher_Validator类的validate方法”。

 

这样,“Student_Or_Teacher_Validator”就可以省略了,只剩下一些原子的“角色类”。要实现Student_Or_Teacher_Validator的验证功能,拿Student_ValidatorTeacher_Validator装配一下就可以了。

 

结论,需要根据实际情况,动态的改变(装配)“Validator接口对象”的validate方法。

 

第一个火花就是“装饰模式”。它可以让客户端动态的组装对象的方法。真神奇!

第二版来啦

o_4.JPG

 

注:上图中出现了AndRelationAnd的一系列角色类,可以暂时省略不看,因为前面说了,现在还没有“与”关系这个需求。只看Or的就可以。

 

我喜欢叫这个模式为洋葱模式,一层包一层,最外层对象某方法的逻辑是由内部一层一层对象的同一方法组合出来的。

 

使用了这个模式,便不用如一版那样实现那么多子类,只要实现几个“角色类”即可,这里有3个(学生角色:Or_Student、教师角色:Or_Teacher、游客角色:Or_Guest)。所有一版中别的子类都可以由这3个组装出来。

 

如要生成一版中的Student_Or_Teacher_Validator对象,可以用Or_StudentOr_Teacher两个对象“Or”出来:

 

1Validator validator = new Or_Student(new Or_Teacher(OrRelation(req)));

 

如要生成一版中的Guest_Or_Student_Or_Teacher_Validator对象,可以用Or_StudentOr_TeacherOr _Guest三个对象“Or”出来:

 

1Validator validator = new Or_Student(new Or_Teacher(new Or_Guest(OrRelation(req))));

 

 

这种一层包一层的new方式,是不是很像洋葱?第一次看是很不习惯,看多了就觉得习惯了。

对客户端的影响:

一版中,客户端要什么样的验证类,就直接使用具体类。

二版中,客户端要什么样的验证类,它的工作多了那么一丁点,它需要先组装一下,正如上面的例子。这种组装的方法很易于理解和使用,不会给客户端带来任何的不便。如果实在觉得客户端组装不出来(傻B客户端),也可以搞个工厂给它supply

优点:

相对一版最明显的优点就是类的数目少了很多。

 

一版不是说“指数级”吗?这里只是线性的了。假设某一天系统拓展到有10个角色,一版就有210次方那么多个,也就是1024个验证类。

 

而二版还是10个角色类,别的都可以在客户端使用的时候,动态的组装

 

更重要的是代码结构好了,重复逻辑少了。每个逻辑都以最atomic的大小放到最应该的地方。

 

进而,维护的代价少多了。如某天“教师角色”的验证逻辑发生了变化,只要改动Or_Teacher一个地方即可。

                 MARCO ZHANG 2006年2月18日23:49:56

posted @ 2006-02-18 23:53 marco 阅读(1230) | 评论 (5)编辑 收藏

    说起这个工厂模式,一时还真不知道该如何说起。反正这是我的开发日志,不提理论的东西,理论的东西那里都有,我只想把具体实践记录下来给师弟师妹们作个参考,积累点经验。所有这些文字都是集中讲一点――“在什么情况下为什么用某种模式好,为什么好,为什么在那种情况下能想起来用?”。

       研究生院项目中“明显”使用了“工厂方法模式”。其实在遇到具体问题的时候,即使我们不知道有这个模式存在,我们也肯定会造一个类似的东西出来。但是,肯定没有书上论述的那么好,那么全面。我想这就是看书的好处吧。

 

工厂方法出现的必然(我的理解,一个很狭隘并幼稚理的人的理解)

 

       刚开始使用这个东西的时候,只是感觉是单纯的一种模式,用于创建需要的对象。

       但是随着使用和思考的深入,越发发现它给我的启示不只在于单纯的对象创建,而是告诉我应该怎么理解“产品”,怎么得到“产品”,怎么消费“产品”,以至于以后怎么设计“产品”。

       下面这个线索是我对它出现必然性的理解:

1.         “针对接口编程”

  这是OO世界中经典的规范,不管你主动还是被动,你天天都在用这个东西。

    接口是共性的表示,在对象的世界中,共性和个性的辩证关系是最重要的关系。在万千的对象中,通过它们之间的共性和个性,可以形成最基本对象层级架构。

假设我们的讨论域中有以下一些对象:“学生”、“大学生”、“小学生”、“中学生”;我们不用细想,学过一天OO的人都可以为这些耳熟能详的对象们,通过个性和共性的关系得出下面的结构图。

o_1.JPG

 

       把这些对象之间的关系定义成这样是顺理成章的。

       下一步肯定是让客户端“使用”这个接口啦。也就是如下图:

 

o_2.JPG

2.         接口和具体类的矛盾

勿庸置疑,我们只希望Client跟接口Student打交道,让它根本就不知道Student有哪些子类,绝对不希望直接跟它们打交道。

但这里出现的困难是,接口都是“假”的,都是由具体类upcast的。

如果Client要使用接口StudentClient中必须会出现下面的代码:

Student marco = new Small_Student();

       只要一出现这个代码,就说明Client不只跟Student打交道了,它知道了Small_Student类,这违反了我们预先的想法。

3.         找“人”帮我去创建“接口对象”

    从上图体现出来的结构看,Client只想跟Student打交道的目的是实现不了的了。

       最简单的方法就是找另外的“帮手”去帮我生成这个“接口对象”。这个帮手它知道“接口对象”的具体类型,但是它为客户端提供的却一定是“接口类型”。这就符合我们的要求了。如图:

o_3.JPG

    这样,Client就可以既用到了“Student接口对象”,又不用因为“只有具体类才能创建对象”的规则,而必须对其子类结构有完全的了解。它成功的解决了2中的矛盾。

       而“负责创建具体类对象的任务”全部都落在了这个“帮手”身上,这个“帮手”(Student_Factory)就是工厂模式中的工厂类,更具体的说,它就是“简单工厂模式”中的“简单工厂类”。

       我觉得,即使一点都不知道工厂模式,一旦我遇到了2里说的矛盾,我也会用这样的方法处理。

4.         这个“帮手”不符合“开-闭原则”

这个帮手的确不错了,而且一跃成为系统中最重要的对象了,所有“创建具体类的逻辑”都放进去了,也就是因为重要,万一挂了不就惨了。

再者,它不符合“开-闭”原则,我不能在不修改这个帮手的情况下添加任何一个产品。在这个例子中就是,如果那天我有病非要加一个“幼儿园学生”进来,那您就必须修改这个“帮手”的代码了,这个“帮手”现在就变成Version2(如下图)了,这个二版的帮手,可以在“代码”层实现对一版(还没有添加幼儿园学生之前)的通用,但这种保证在“开-闭”原则看来,还是不够的,不保险的,它要的是在类的结构上的保证。声明一下,这是我很感性的理解,不正确的可能性很高。

o_4.JPG

5.         让“帮手”也多态

这里可以尝试让“帮手”也多态一下,这样“每种学生类创建的任务”都被分派到了多态出来的类中去了。这时,再有新的学生类型加进来,添加一个对应的帮手就可以了。这样虽然类多了一些,但是符合“开-闭”原则,书上称之为“工厂方法模式”。如图:

o_5.JPG

    假如Client现在要使用一个小学生,代码如下:

1        //创建一个小学生工厂类这个帮手
2        Student_Factory factory = new Small_Student_Factory();
3        //求助这个帮手,帮我创建一个

4        Student primaryStudent = factory.produce();
5        //这时就可以使用这个小学生了,让它玩玩游戏吧

6        primaryStudent.playGames();
7

    在这里还是要强调两点:

n         虽然实际上Client的确使用了一个小学生对象(Small_Student),但这里Client也认为它就是Student对象,这里一定要用Student接口来隐藏它的具体类。

n         另外,却不需要用Student_Factory这个接口来隐藏它的具体类,因为,Client实际就是通过“选择它的具体类”这招儿来“选择创建的学生类型”。这里的Student_Factory更多的功能不是“隐藏”具体类,而是“规范”具体类。

 

项目实践

 

       扯淡到此,该联系我们的项目啦。

       由于是做研究生院的项目,其中巨大的需求就是要让同学能在网上提交各种申请单,申请退学的,申请转专业的,申请复学的,申请保留学籍的,除了申请女朋友的外,应有尽有。       对于这些单子,用最最基本OO思维,根据共性个性分析方式,抽象出一个申请单接口,和若干的具体类。

       当然,除了概念上感性上吻合以外,在项目中它们也要“真”的有巨大的共性才行,如都要提交,修改,删除,审核,打印等功能。

       靠,既然都这样了,肯定就用一接口规定它了。

       想到这里,加上有了上面的思考,不用说了,就用工厂方法模式啦。

 

o_6.JPG

    图中Ent_Shift就是申请单接口。等于前面分析的Student接口。还可以看到有很多具体类,前面例子中是代表各种学生,这里就是代表各种申请单。

       当然,这里还有很多工厂,也就是前面一直叫的“帮手”。

o_7.JPG

       Bean_Shift就是工厂接口,相当于前面的Student_Factory接口。还有很多的具体类就是生产各种申请单的工厂类。

       下面就是使用“申请单工厂方法模式”的一段客户端代码:

 1        //声明申请单接口
 2        Ent_Shift es = null;
 3        //声明申请单工厂接口

 4        Bean_Shift bs = null;
 5        //根据传入的申请单类型数字身成对应的申请单工厂

 6        switch (shiftTypeID) {
 7            case
 Bean_Shift.SHIFT_CHANGETEAAP :
 8                bs = new
 Bean_Shift_CHANGETEAAP();
 9                break
;
10            case
 Bean_Shift.SHIFT_RESERVEAP :
11                bs = new
 Bean_Shift_RESERVEAP();
12                break
;
13            case
 Bean_Shift.SHIFT_RENEWAP :
14                bs = new
 Bean_Shift_RENEWAP();
15                break
;
16             //省略了别的申请单……………………

17            default :
18                this.forwardErr(req, resp, "所选择的异动类别不存在"
);
19        }

20
21        try 
{
22            //调用工厂接口的生产方法

23            es = bs.getBlankShift(stuID);
24
            
25        }
 catch (Exception e) {
26            this
.forwardErr(req, resp, DB_ERROR);
27        }

28        //调用单子的提交方法
29        es.submit(req);
30
        
31        //发给页面去显示

32        es.fowrardWithSessionObject(
33
                req,
34
                resp,
35
                Ent_Shift.nameInSessionAndRequest);
36

 

升华

 

       个人比较同意《Design Pattern Explained》中作者讲的,要用好很多的模式,其中都有一个思路,就是用接口或抽象类来隐藏子类的不同。

       我每当看到这时,老是会被一种思路困扰――“new只能new具体类啊,这咋能隐藏呢,这隐藏还有什么用呢?”。

       作者仿佛也曾经有过我的这个傻B苦恼,它的解决方法就是:根本在使用对象的时候,特别是设计阶段,尽量不去想对象是在那里被new的。他认为:反正有了工厂模式后,你总有办法把他们new出来的。

       所以,我用了工厂模式后更发的启发是:以后设计的时候不要想一个Client怎么创建一个对象,尽管放心大胆的先继续想,直接使用就好了。反正最后我还有工厂模式呢。

       因此俺的副标题才是“Ignore how they were created”,呵呵。
                                    MARCO ZHANG 2006年2月16日3:52:10

posted @ 2006-02-16 03:53 marco 阅读(1380) | 评论 (9)编辑 收藏