Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » Design Pattern & UML  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 【转载】Thinking in OO
dalefirst



发贴: 0
积分: 0
于 2002-11-24 17:42 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
http://lbs.servehttp.com/phpBB2/viewtopic.php?t=207

作者:robbin

在实际的软件项目中,我的看法是,对于一个应用软件项目的开发来说,OOAD的引入一定会带来开发效率和维护效率的提高。至于OOAD的项目是否一定要用UML,这也不一定,我见一个软件公司做的一个几百万的J2EE的项目,他们写的设计文档完全是语言的描述,和excel表格列出来所有的类。只不过我觉得这样的设计文档可读性未必很高,设计也未必合理。

我是从学习java编程开始接触OOP(面向对象编程),开始使用Java写程序的时候感觉特别别扭,因为在此之前,我主要用的编程语言是C,还用了很长时间的PHP,很欣赏类C语言的简洁性和高效性,特别是因为做过系统方面,比如Unix和Oracle的管理,所以对于系统的优化意识很强,不能容忍哪怕一点点的性能的无谓损失,所以对于软件代码很有洁癖,喜欢C语言的简练而表达能力丰富的风格。特别受不了Java运行起来慢吞吞的速度,写的代码冗长罗嗦,本来一个很简单的事情,要写好多类,一个类调用一个类的实现,心里的抵触情绪比较强。

在那个时候,我需要写对Oracle数据库的操作,本来想用C的,但是研究了半天,发现不论是OCI接口也好,还是PROC也好,一方面是在Linux上面配置起来异常麻烦,因为还涉及到Glibc2.2和Oracle8.1.7的不兼容性的问题;另一方面是开发和维护都不方便,因为编译的时候要联接一堆库函数,要配置好include路径,有一点不对,就编译错误。PROC的编译前的配置文件很难配对,我从来就没有成功过。OCI到是配对了,也编译运行对了,但是OCI的库函数实在是太难用了!函数调用的参数异常的复杂,我研究过Oracle的例子,是自己封装了OCI库函数,但是并不完善,也不通用。曾经在网上找到一个人用C++写的封装OCI的类,调用起来很简单,可惜的是只能在Windows上面用。何况我就是研究通了OCI,这样的经验在别的数据库也用不上,每种数据库都有自己完全不同的数据库API,几乎是换一种数据库,就要重新学一次,因此对C比较失望。

其实C语言的最大问题,何尝不是这点呢?ANSI C到是很简单的,但是涉及到具体的编程,会发现离目标还差的很远,你还需要用学习C语言多的多的精力去啃特定的库函数。比如说在Linux上面编程,你得随时查Glibc手册;在KDE上编程,要啃Qt;数据库编程,就更要命了。

那个时候,我还需要做一些HTTP协议的编程,研究了一下Glibc,发现根本没有HTTP协议的库函数,那意味着我得对Socket编程,自己写代码来实现HTTP,实在是太夸张了!虽然有一个libwww库可以用,但是问题是这些库都不是标准的库,就像Oracle自己的Sample里面封装的OCI函数一样,并不通用,所以也不放心在自己的程序里面用。

而这个时候,却发现我的困扰用Java却可以轻易的解决,只需要很少量的JDBC和Servlet代码就解决问题了,就这样义无反顾的投入了Java。

对Java的面向对象的特性自己琢磨了很久,也自认为有所领悟,也开始有意识的运用OOP风格来写程序,然而还是经常会觉得不知道应该怎样提炼类,面对一个具体的问题的时候,会觉得脑子里千头万绪的,不知道怎么下手,一不小心,又会回到原来的思路上去。

记得那个时候,要发垃圾邮件,垃圾邮件列表存在数据库里面。倘若用C来写的话,我会觉得很容易做,我会先把邮件内容读入,然后连接数据库,循环取邮件地址,调用本机的qmail的sendmail命令发送。

然后我准备用Java来写,既然是OOP,就不能什么代码都塞到main过程里面,总归要写的可复用的类吧,于是就写了三个类,一个类是负责读取数据库,取邮件地址,调用qmail的sendmail命令发送,一个类是读邮件内容,MIME编码成HTML格式的,再加上邮件头,一个主类负责从命令读参数,处理命令行参数,调用发email的类,来完成工作的。

其实大家可以看到,这样的设计完全是从程序员实现程序的功能的角度来分析的,或者说,设计类的时候,是自低向上的,从机器的角度到现实世界的角度的,在设计的时候,就已经把程序编程实现的细节都考虑进去了,企图从底层实现程序这样的出发点来分析问题,来达到满足现实世界的需求。

这样的分析方法其实是很失败的,完全不适用于Java这样的语言。其实改用C语言,封装两个C函数,都会比Java实现起来轻松的多,逻辑上也清爽的多。

这样错误的观念,我是在开始学习UML以后,才扭转过来的。我觉得OOAD的精髓在于考虑问题的思路是从现实世界的人类思维习惯出发的,领会了这一点,就领会了OOAD。

举个简单的例子:假使现在我们要写一个网页计数器,访问一次页面加1,计数器是这样来访问的

http://localhost/count.cg?id=xxx

后台有一个数据库表,保存每个id的计数器当前值,请求页面一次,对应id的计数器的字段update一次加1(这里我们忽略并发更新数据库表,出现的表锁定的问题,其实我发现sina的收费个人主页的计数器就是这样做的,不信,大家可以用ab来请求一下sina的个人主页计数器,只消并发5个以上的请求数,计数器就完蛋了,半天才能恢复)

如果按照一般从程序实现的角度来分析,我们会这样考虑:首先是从HTTP环境里取到id的值,然后查数据库表,获得id对应的字段值,取出来加1,更新数据库,然后生成一段Javascript代码document.write(计数器计数值)

可是假使是一个完全不会写程序的人,他会怎样来考虑这个问题的呢?他会这样想:
我需要有一个计数器这样的东西,这个计数器应该有这样的功能,刷新一次页面,访问量就会加1,另外最好还有一个计数器清0的功能,当然计数器如果有一个可以设为任意值的功能的话,我就可以做弊了。

所以计数器是这样工作的,计数器使用访问量加1功能。That's all!

做为一个不懂程序设计的人来说,他完全不会考虑到对数据库会如何操作,对于HTTP变量该如何传递。他考虑问题的角度就是我有什么需求,我的业务逻辑是什么,我怎样来实现我的业务逻辑。

按照上面不懂程序设计的人的思路(注意,他的思路其实就是我们平时在生活中习惯的思维方式)
我们知道需要有一个计数器类 Counter,有一个必须的和两个可选的功能

getCount() // 取计数器值
resetCounter() // 计数器清0
setCount() // 设计数器为相应的值

进一步细化,用Java代码表示:

public class Counter {

public int getCount(int id) {

}

public void resetCounter(int id) {

}

public void setCount(int id, int currentCount) {

}

}

到这里,我就可以开始写主程序了。在count.cgi里面,我会这样调用

// 这里从HTTP环境里面取id值
Counter myCounter = new Counter();
int currentCount = myCounter.getCount(id);
// 这里向客户浏览器输出

基本上程序的框架都写好了,剩下的就是实现方法里面具体的内容了。

我们从上面的例子中可以发现,OOAD的思路其实就是我们在现实生活中习惯的思维方式,是从人的考虑问题的角度出发,然后把人类解决问题的思维方式逐步翻译成程序能够理解的思维方式,软件也就设计好了。

在这里,我觉得最容易犯的错误就是容易一开始分析就想到了程序代码实现的细节,然后封装的类完全就是基于程序实现的功能,而不是基于解决一个具体问题的逻辑。

典型的错误问法是:“你告诉我你怎么封装一个数据库的select?”

我不会去封装select语句的,我只封装解决问题的业务逻辑,对数据库的读取是在业务逻辑的编码实现的时候才考虑的问题。

回过头看上面那个发垃圾邮件的例子,应该如何应用OOAD呢?

对于一个邮件来说,有邮件头,邮件体,和邮件地址,然后我发送邮件,需要一个发送的方法,另外我还需要一个能把所有邮件地址列出来的方法。所以应该如下设计:

类JunkMail 实现了一个枚举接口,支持next() 和 hasNext() 方法。
属性:
head
body
adddress
方法:
sendMail() // 发送邮件
next() 取下一个邮件地址
hasNext() 判断是否还有邮件地址

说明: iterator接口定义了两种操作,next() 和 hasNext()

进一步细化:

public class JunkMail implements iterator {

private String head;
private String body;
private String address;

public JunkMain() { //默认的类构造器
// 从外部配置文件读邮件头和邮件体
this.head=...;
this.body=...;
}

public boolean sendMail(String address) {
// 调用qmail,发送email
}

public String next() {
// 从数据库取当前记录,游标移动到下一个记录
}

public boolean hasNext() {
// 判断游标是否移到记录集末尾
}
}

在主程序里面,就这样来调用

JunkMail mail = new JunkMail();
while (mail.hasNext())
mail.sendMail(mail.next());

程序写起来又轻松,又惬意,感觉好多了。

而且在用Java写程序的时候,我更习惯是简单的定义一下被调用类的原型,然后就可以把主程序完整的写好了,在实现了主程序之后,对于被调用的类应该怎样设计,具备哪些属性和方法就很清楚了,最后再回过头来实现底层被调用类的代码。

如果说传统的面向过程的编程是符合机器运行指令的流程的话,那么OOAD和OOP就是符合现实生活中人类解决一个具体问题的思维过程。

从上面的例子就可以看出来,对于支持面向对象的编程语言来说,即便是一个很小的程序,运用OOAD和OOP也可以带来很好的实际效果,不论是开发效率,还是程序质量,还是可维护性和可扩充性。对于很大的项目来说,越是复杂,就越不可能一下子把整个系统的实现细节考虑清楚,那么这种OOAD,从现实生活思维逐步导入到机器运行指令的设计方法就显得更加有优势。

对于一个很庞大的系统来说,即使是用OOAD,想要设计的很合理,也是一件很困难的事情,需要具有很丰富的经验的系统分析师才行,然而按照OOAD设计好的软件系统,编码实现起来确确实实变的容易了很多,效率也要高很多。特别是后期维护和将来的软件扩充和升级。

以我的经验来看,对于开发团队进行OOAD培训的代价并不高,我的办法是先给大家布置一个任务,他们做了有三四天时间,做好以后,我再把自己按照OOAD来设计好的方法交给他们,让他们重新做,再花半天时间就可以做好了。这样对比着来培训,效果比较好。

运用UML进行OOAD的设计的时候,从用例图导入到合作图,再到序列图、类图,几乎是强制性的使用OOAD的思维习惯。一方面,UML的符号可以很好的表达OOAD设计思想,另一方面,这种强制性也在很大程度上保证了不会过早的考虑程序实现的细节,从而避免了典型的错误思维。

UML的内容很丰富,在实际运用中,大部分情况下也就是用到用例图,序列图和类图而已,很复杂的应用会用到合作图,也许还有活动图。掌握起来也很快,学习的成本并不是很高。

OOAD和UML是易学难精的东西,本身的学习门槛很低,但是真的能在实际工作中运用的很好,并不是每个人都可以做到的,也许最终还是要靠个人的基本素质和逻辑思维能力吧。

在umlchina上面,我发现有些人特别钻UML的技术细节,比如说会争论到底是一个用例好,还是分割成几个用例好,用例之间的关系该画成include关系,还是extends,等等。这样的话又陷入了为UML而UML的"唯技术论"了。学以致用为本,大家都不是做学问的人,抠那些细节,把大好的时间都浪费掉了。我带他们做项目也是这样的,一帮人争论用户权限设定该用一个用例还是几个用例,其实你管它几个用例呢?只要你能通过自己的分析把问题理解清楚了,并且不会在下面的设计过程中产生误导就OK了,不管黑猫白猫,只要把系统设计得最合理,就是好猫。




flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923