颠覆软件

关注 : 架构与设计,敏捷,快速开发,项目管理,执行力,SSH,RoR

Archive for September, 2006

[zt]设计模式之Visitor 访问者模式

September 28, 2006

设计模式之Visitor

板桥里人 http://www.jdon.com 2002/05/05(转载请保留)

模式实战书籍《Java实用系统开发指南》

Visitor访问者模式定义
作用于某个对象群中各个对象的操作. 它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.

在Java中,Visitor模式实际上是分离了collection结构中的元素和对这些元素进行操作的行为.

为何使用Visitor?
Java的Collection(包括Vector和Hashtable)是我们最经常使用的技术,可是Collection好象是个黑色大染缸,本来有各种鲜明类型特征的对象一旦放入后,再取出时,这些类型就消失了.那么我们势必要用If来判断,如:
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Collection)
messyPrintCollection((Collection)o);
else if (o instanceof String)
System.out.println(“‘”+o.toString()+”‘”);
else if (o instanceof Float)
System.out.println(o.toString()+”f”);
else
System.out.println(o.toString());
}
在上例中,我们使用了 instanceof来判断 o的类型.

很显然,这样做的缺点代码If else if 很繁琐.我们就可以使用Visitor模式解决它.

如何使用Visitor?
针对上例,定义接口叫Visitable,用来定义一个Accept操作,也就是说让Collection每个元素具备可访问性.

被访问者是我们Collection的每个元素Element,我们要为这些Element定义一个可以接受访问的接口(访问和被访问是互动的,只有访问者,被访问者如果表示不欢迎,访问者就不能访问),取名为Visitable,也可取名为Element。

public interface Visitable
{
public void accept(Visitor visitor);
}

被访问的具体元素继承这个新的接口Visitable:

public class StringElement implements Visitable
{
private String value;
public StringElement(String string) {
value = string;
}

public String getValue(){
return value;
}
//定义accept的具体内容 这里是很简单的一句调用
public void accept(Visitor visitor) {
visitor.visitString(this);
}
}

上面是被访问者是字符串类型,下面再建立一个Float类型的:

public class FloatElement implements Visitable
{
private Float value;
public FloatElement(Float value) {
this.value = value;
}

public Float getValue(){
return value;
}
//定义accept的具体内容 这里是很简单的一句调用
public void accept(Visitor visitor) {
visitor.visitFloat(this);
}
}

我们设计一个接口visitor访问者,在这个接口中,有一些访问操作,这些访问操作是专门访问对象集合Collection中有可能的所有类,目前我们假定有三个行为:访问对象集合中的字符串类型;访问对象集合中的Float类型;访问对象集合中的对象集合类型。注意最后一个类型是集合嵌套,通过这个嵌套实现可以看出使用访问模式的一个优点。

接口visitor访问者如下:

public interface Visitor
{

public void visitString(StringElement stringE);
public void visitFloat(FloatElement floatE);
public void visitCollection(Collection collection);

}

访问者的实现:

public class ConcreteVisitor implements Visitor
{
//在本方法中,我们实现了对Collection的元素的成功访问
public void visitCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Visitable)
((Visitable)o).accept(this);
}

public void visitString(StringElement stringE) {
System.out.println(“‘”+stringE.getValue()+”‘”);
}
public void visitFloat(FloatElement floatE){
System.out.println(floatE.getValue().toString()+”f”);
}

}

在上面的visitCollection我们实现了对Collection每个元素访问,只使用了一个判断语句,只要判断其是否可以访问.

StringElement只是一个实现,可以拓展为更多的实现,整个核心奥妙在accept方法中,在遍历Collection时,通过相应的accept方法调用具体类型的被访问者。这一步确定了被访问者类型,

如果是StringElement,而StringElement则回调访问者的visiteString方法,这一步实现了行为操作方法。

客户端代码:

Visitor visitor = new ConcreteVisitor();

StringElement stringE = new StringElement(“I am a String”);
visitor.visitString(stringE);

Collection list = new ArrayList();
list.add(new StringElement(“I am a String1″));
list.add(new StringElement(“I am a String2″));
list.add(new FloatElement(new Float(12)));
list.add(new StringElement(“I am a String3″));
visitor.visitCollection(list);

客户端代码中的list对象集合中放置了多种数据类型,对对象集合中的访问不必象一开始那样,使用instance of逐个判断,而是通过访问者模式巧妙实现了。

至此,我们完成了Visitor模式基本结构.

使用Visitor模式的前提
使用访问者模式是对象群结构中(Collection) 中的对象类型很少改变。

在两个接口Visitor和Visitable中,确保Visitable很少变化,也就是说,确保不能老有新的Element元素类型加进来,可以变化的是访问者行为或操作,也就是Visitor的不同子类可以有多种,这样使用访问者模式最方便.

如果对象集合中的对象集合经常有变化, 那么不但Visitor实现要变化,Visistable也要增加相应行为,GOF建议是,不如在这些对象类中直接逐个定义操作,无需使用访问者设计模式。

但是在Java中,Java的Reflect技术解决了这个问题,因此结合reflect反射机制,可以使得访问者模式适用范围更广了。

Reflect技术是在运行期间动态获取对象类型和方法的一种技术,具体实现参考Javaworld的英文原文.

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]成为软件架构师

September 22, 2006

key words:软件架构师
转自here

现在软件架构师满天飞,是个写代码的都称自己为软件架构师,就象开个公司管上四五号人就给自己按个CEO头衔一样,着实让人好笑。于是到网上GOOGLE了一下看看软件构架师具体是个啥东东,有想做货真价实的构架师,就朝着那方向努力吧。网摘如下:

软件架构师的职责:将客户的需求转换为规范的开发计划及文本,并制定这个项目的总体架构,指导整个开发团队完成这个计划。

软件架构师的具体工作:
(1)在需求阶段,软件架构师主要负责理解和管理非功能性系统需求,比如软件的可维护性、性能、复用性、可靠性、有效性和可测试性等等,此外,架构师还要经常审查和客户及市场人员所提出的需求,确认开发团队所提出的设计;
(
2)在需求越来越明确后,架构师的关注点开始转移到组织开发团队成员和开发过程定义上;
(
3)在软件设计阶段,架构师负责对整个软件体系结构、关键构件、接口和开发政策的设计;
(
4)在编码阶段,架构师则成为详细设计者和代码编写者的顾问,并且经常性地要举行一些技术研讨会、技术培训班等;
(
5)随着软件开始测试、集成和交付,集成和测试支持将成为软件架构师的工作重点;
(
6)在软件维护开始时,软件架构师就开始为下一版本的产品是否应该增加新的功能模块进行决策。

软件架构师的要求
(1)必须对开发技术非常了解,具有丰富的软件设计与开发经验,关键时候能对技术的选择作出及时、有效的决定。
(
2)有良好的组织管理能力:沟通、领导、团队协作
(
3)构件通信机制方面的知识:远程调用、JAVARMI、CORBA、COM/DCOM、各种标准的通信协议、网络服务、面对对象数据库、关系数据库等等

成长为软件架构师的几个阶段:
(1)构架师胚胎(程序员):语言基础、设计基础、通信基础等,内容包括java、c、c++、uml、RUP、XML、socket通信(通信协议)
(
2)构架师萌芽(高级程序员):分布式系统组建等内容,包括分布式系统原理、ejb、corba、com/com+、webservice、网络计算机、高性能并发处理等
(
3)构架师幼苗(设计师):透彻掌握设计模式,包括设计模式(c++版本、java版本)、ejb设计模式、J2EE构架、UDDI、软件设计模式等。此期间,最好能够了解软件工程在实际项目中的应用以及小组开发、团队管理

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

在IDEA中集成VSS版本控制

September 21, 2006

key words: VSS IDEA

vss实在是太恶心了,提交各东西还不支此右键,要是要在开发一段时间后提交多个不同的目录下的文件费死劲了,我压根不知道哪个文件还没有提交,Fuck Microsoft!

至少,我们还有IDEA  :)   在开发工具里提交的一个好处就是直观和简便.Let’s go!

具体指导请看这里
你需要填写关于VSS的相关信息 ,截图如下:
vss.png

其中的vss project可能有点困难,属于vss自己的术语,你自己实际上是不知道的,方法是打开vss客户端,通过这个工具查找到你所在的项目的vss project到底是什么,右键项目名称,出现如下dialog

vss2.png
OK了

在IDEA里看看效果,截图如下:

vss3.png

Fine.

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

DAO模式演变

September 21, 2006

key words: DAO模式

今天在看一篇文章时提到了DAO,这个东西以前也经常接触,突然想回顾一下,于是打开Appfuse里看看dao模式(记忆中appfuse里就是很多的dao)

截图如下:
appfusedao.png

很清楚,左边的部分是基础模块,原意是想让右边的DAO和实现能够重用左边的,可是我找了半天也没看到需要重用左边的东西,因为在client调用的所有方法中都是明确的getUser或removeUser,就是没有getObject或者removeObject,那么不禁要问,左边的基础dao和它的实现还有什么意义呢?所以我的第一想法就是把左边的去掉得了,还好,果然有支持我想法的做法,打开springside,我们看到如下的结构:
截图2

 

springside.png
这里的做法更厉害,连interface也不要了,不过效果确实是很简洁,在bookmanager里完全重用了左边的基本方法 :

public Book get(Integer id) {
return (Book) super.get(id);
}

public void save(Book book) {
super.save(book);
logger.info(
保存图书.图书详情: + book.toString());
}

public void remove(Integer id) {
super.remove(id);
logger.info(
删除图书.图书ID: + id);
}

这是一个更务实的做法,如果你的项目并不是那么那么的复杂完全可以这么做,当然要说其有什么缺点显然和没有了interface的天生属性决定了的,不可强求,若你对测试隔离面向接口以及你能想到的所有关于interface的好处,那就用你自己的方式吧。

现在我在想一个问题,难道appfuse里的继承的基本关于object的做法就没有地方可用了么?


其时正好碰到java视线这一篇文章有点相关,你可以参考一下:
用DAO模式有什么好处?


ps:
以前是一个基本的dao,然后n个业务dao继承于这个基本dao,现在提供一个通用dao,每个要用到的地方直接继承用就是了,更务实了!
不过,有一个小小的瑕疵,就是对于service中类似getUserByName或者getPeopleByEmail方法中需要提供给dao一个sql语句,从mvc的角度看,在service中看到了db层,有点不雅,不过综合来看这个还是可以或略,不要专牛角尖嘛  :)

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]Java 学习方法浅谈

September 20, 2006

转自 robin

Java本身是一种设计的非常简单,非常精巧的语言,所以Java背后的原理也很简单,归结起来就是两点:

1、JVM的内存管理

理解了这一点,所有和对象相关的问题统统都能解决

2、JVM Class Loader

理解了这一点,所有和Java相关的配置问题,包括各种App Server的配置,应用的发布问题统统都能解决

就像张无忌学太极剑,本质就是一圈一圈的画圆,你要是懂得了太极剑的本质,那么太极剑就那么一招而已,本身是很容易学的,只是难度在于你要能够举一反三,化一式剑意为无穷无尽的剑招,这就需要一点悟性和不断的实践了;反过来说,如果学剑不学本质,光学剑招,你就是学会了1万招,碰到了第1万零1招,还是不会招架,败下阵来。

技术世界本来就是丰富多彩,企图统一标准,实际上也做不到,但是世界本质其实并不复杂。学习技术,特别是某种具体的软件工具的时候,应该学会迅速把握事物的本质,不要过多搅缠细节。软件工具应该为我所用,而不是我被工具所驾驭。当你具备了对整个J2EE架构的设计和实施的能力,你还会被具体的工具束缚吗?哪种工具适合你的架构,你就用什么,哪种不适合你,你就抛弃它,软件皆臣服于你的脚下,而不是你被什么软件牵着鼻子走,到了这种程度,你难道还害怕学习什么新的软件?

我自己也在一直朝着这个方向努力,在我心中,设计软件,架构是第一位的,采用什么技术要为架构服务。如果我发现什么技术对我的架构来说很重要,那么我会花时间去学习,去钻研,就像我花时间去钻研ORM一样,如果我觉得什么技术对我的架构来说没有用,即使技术再火爆,我也不去碰它

总之要学会抓住本质,驾驭技术,而不是被技术所驾驭。当你掌握了本质原理,其实学什么都很快,毕竟都是相通的,我先看JDO,后看 Hibernate,其实两者就很类似,所以学得很快,以后如果有工作需要,要我学习别的ORM,那我也不会觉得有什么困难的,一样手到拿来。

更有说服力的是Unix类的操作系统,那就更相似了,只要抓住了Unix最本质的几点,例如shell命令和编程,文件系统结构和配置,系统启动原理和过程,所有的Unix都是无师自通的。我自己会用Linux,FreeBSD,SCO Unix, Solaris,HP-UX 和 AIX等6种Unix,更体会到一通百通的道理。

拿刚出了光明顶密道的张无忌来说吧,(我很喜欢张无忌这个角色),他也没有练过什么武功,但是他已经把天下武学之本质:九阳神功 + 乾坤大挪移学会了,所以不管什么功夫,他都是看一遍就会,马上为我所用,看了空性用了一遍龙爪手,就会用龙爪手来破对方;和昆仑派打了一架,就会用昆仑剑法和灭绝师太过招;七伤拳更是无师自通;太极拳也是看一遍就会。

总之,学习方法还是很重要,别被五花八门的技术给搞不清学习方向了。

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

Oracle分页的一个问题

September 20, 2006

key words :Oracle分页 视图

google了一下关于Oracle的分页方法,方法还不少,大多数效果差不多-有点恶心. 恶心也要作,不过后来就是大家都用得这种方式在我这里出现了新问题,奇怪的是怎么没有别人碰到?

String condition =  teacher_id =  + userId +  and school_id=+siteId;
sql 
=
 select *  +
 from your_table where  + condition +
 and rowid not in ( select rowid from your_table where + condition +
 and rownum <=  + (pageIndex - 1* Constants.PAGE_NUMS +  +
 and rownum <=  + Constants.PAGE_NUMS ;
现在的问题是我需要按照table的某个字段排序,于是改成如下:

String condition =  teacher_id =  + userId +  and school_id=+siteId;
sql 
=
 select *  +
 from your_table where  + condition +
 and rowid not in ( select rowid from your_table where + condition +
 and rownum <=  + (pageIndex - 1* Constants.PAGE_NUMS + order by id desc +
 and rownum <=  + Constants.PAGE_NUMS + ” order by id desc“;
这个sql有问题么?
答案是可能有问题,也可能没有问题,因为据说在8i的Oracle版本之前都不行,实际上也不尽然,在我的9i和10g我得到的是同样的错误 “missing right parenthesis“,还有一位兄弟估计是DBA建议我去metalink打一个patch,埃,动作太大了,不敢动。

问题还是要解决,试了下类似于select a.*,rownum r from (select * from table where …) a where rownum < 10 等的方法,效果一样,就是不能加嵌套的order by
最后,用视图的方法间接解决问题,因为我要解决的问题实际就是按某个字段排序,那么在视图里先对table进行排序,再在视图的基础上作操作就OK了.

另,还有一种不错的实现方法,即用OracleCachedRowSet,分页也比较简单,有点类似于hibernate,由于时间关系没有时间去看,感兴趣的朋友可以看一下.

BTW: 对于视图可能rowid有问题,可以改成视图的某个主键替换

String condition =  teacher_id =  + userId +  and school_id=+siteId;
sql 
=
    
 select *  +
    
 from your_table where  + condition +
    
 and id not in ( select id from your_table where + condition +
    
 and rownum <=  + (pageIndex - 1* Constants.PAGE_NUMS + order by id desc)  +
    
 and rownum <=  + Constants.PAGE_NUMS +  order by id desc;

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]领域相关的模型里面需要3种对象

September 19, 2006

key words :  模型,vo,po

 

转自portian

基本上一个应用程序里面的领域相关的模型里面需要3种对象:
1。值对象(Value Object),没有身份,内容表示一切,譬如我和weihello都去银行里面存取100大洋,那这个100RMB是一个值对象

2。实体对象(Entity),需要持久,不是按照内容,而是按照它的身份来区分,也就是说即使内容完全一样,也不是同一个对象。这个身份在内存里面是它的实例地址,在数据库里面是关键字,最常见的就是OID.这个实体对象并不是纯数据,它处理本身的实体模型,例如Accout,它的 withDraw,它的子Account等等,它也处理自己和其他实体对象之间的关系,例如订单里面的订单行,都是应该在这个Account里面实现的,而不应该有一个什么控制类。在一个Web应用程序里面,涉及到对象关系的一般只需要一个(或几个)DTOFactory负责所有对象的DTO和 Entity之间的组装和拆份,不需要专门的管理,这一部分也是和数据建模最相近的地方。

3。服务对象(Service),这是为我们提供服务的类,譬如银行里面服务员,她帮助我们把钱从一个账户转到另外一个账户,并记录相应的交易。

对象的作用是对它自己的内部状态负责,如果它需要存取很多其它对象的状态进行运算,那叫做特性忌妒,是要重构的。应该把这些代码移到那个持有这些状态的类里面


辨别一些名词:
1。VO:实际上很模糊,通常指ValueObject和ViewObject
2. ViewObject,界面展现需要的对象,如Struts的FormBean
3。Value Object,早期被作为ValueObject和Transfer Object的总称。实际上Value Object的真正意义在于它的内容,而不是身份
4。Transfer Object:数据传输对象,在应用程序不同层次之间传书对象,在一个分布式应用程序中,通常可以提高整体的性能
5。PO:也许就是Persistent Object,基本上就是Entity了
在不同的体系结构和实现方式里面,这些对象有可能重复,也有可能不重叠。如果你要做一个对所有的体系都能够方便移植的框架,那么每一种对象都需要严格区分。例如JDO的PO不能作为TO,应为它不能脱离PM,譬如你可以选择用ViewObject(如Struts的FOrmBean)直接作为 TO,但在tapestry和Webwork里面就不合适了。但在很多时候,能够方便实用是最重要的,不要过度设计就是了。

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]关于Cookie跨域操作遇到的问题及解决方法

September 18, 2006

注:关于跨域登陆cookie的问题在网上搜索了一下,没看到有java下的示例,这个asp的也可以参照一下,有空再在java下测一下.

key words:单点登陆 SSO 跨域cookie

摘要:当你有一个Cookie组(或叫Cookie字典)使用Domain属性指定域名之后,当你在对该组的成员进行修改或新增的时候,一定要在操作之后加上Resonse.Cookies(CookieName).Domain属性。如果没有必要,请不要修改已设置Domain的Cookie组.
关键字:
正文:
Cookie跨域操作看来是个简单的问题,因为只要指定Domain属性为指定网站的根域名就可以了.但是笔者在实际使用过程中却遇到了一些问题,的确值得注意.

  

cookie在www主域名下创建,并写入Domain属性,

如:(为方便调试以下代码皆为asp代码)   Write.asp <%
Response.Cookies(CookieName)(“UserName”) = ”SunBird”
Response.Cookies(CookieName)(“Password”) = ”xyz1234″
Response.Cookies(CookieName).Domain = ”xxxx.com”
%>
  上面文件放在www主域名下,同时在同目录下放置一个读取cookie的Read.asp   Read.asp <%
Response.Write Request.Cookies(CookieName)(“UserName”)
Response.Write Request.Cookies(CookieName)(“Password”)
%>
  再放一个Read.asp文件到另外一个子域名站点里,代码同上。最后我们再做一个清除cookie的Clear.asp放在主域名下   Clear.asp <%
Response.Cookies(CookieName)(“UserName”) = ”"
Response.Cookies(CookieName)(“Password”) = ”"
Response.Cookies(CookieName).Domain = ”xxxx.com”
%>
  

现在可以通过下面的执行顺序来测试,Write.asp–>主域名的Read.asp–>子域名的Read.asp 所有 Read.asp页面都可以读取到Write.asp创建的cookie的值,然后再运行Clear.asp进行清除,一切都Ok,看上去没有什么问题。   但是把这种方法运用到实际的站点时却出现问题了。   问题描述:   第一次登录一切ok,所有子域名都可以访问到主域名存储的cookie,但是,一旦退出之后,子域名的cookie被清除了,但是主域名的 cookie仍然保留着,强行清除主域名的cookie之后,无论怎样登录主域名下都无法保存cookie了,除非关掉浏览器重新打开。   经过多次尝试之后,无意中发现问题所在,以下是测试经过。   创建一个Write2.asp的页面放在主域名下 <%
Response.Cookies(CookieName)(“TEST_COOKIE”) = ”TEST_COOKIE”
%>
  

第一步:关闭浏览器后,按以下顺序执行,Write.asp–>主域名的Read.asp–>子域名的Read.asp 到这里所有Read.asp读取正常。   

第二步:Clear.asp–>主域名的Read.asp–>子域名的Read.asp 到这里清除操作是成功的。 第三步:Write.asp–> Write2.asp –> 主域名Read.asp –> 子域名Read.asp 到这里两个Read.asp都可以读取到cookie的值。   

第四步:重新执行第二步,发现主域名Read.asp仍然输出了值,而子域名下的Read.asp的值已经被清空了。   根据以上测试总结以下几点再跨域使用cookie时需要注意的地方   1、当你有一个Cookie组(或叫Cookie字典)使用Domain属性指定域名之后,当你在对该组的成员进行修改或新增的时候,一定要在操作之后加上Resonse.Cookies(CookieName).Domain属性。

2、如果没有必要,请不要修改已设置Domain的Cookie组,直接使用Response.Cookies(“CookieText”) = CookieValue 来创建一个新的Cookie。

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]做设计的步骤

September 16, 2006

key words: 如何做设计 设计步骤
转自robin

一。需求分析(抽象Use case + 分析Use case之间的关系)

分析软件需求,以用户的角度来使用软件,找出发生的scenerio,抽象成为一个一个Use Case,分析出Use Case之间的关系,这一步是非常重要的,这一步做好了,设计就成功了一半。Use Case的抽象有一些可以遵循的原则,这里不详细谈。

然后用语言描述每一个Use Case,描述用户使用一个Use Case发生的主事件流以及异常流。

这样就完成了需求分析阶段。

二。概要设计(找出实体 + 分析实体类之间的关系 + 提取控制类 + 画序列图)

接下来做概要设计,针对每个Use Case,读Use Case的描述,看事件流,找出所有的实体类,这也有一些可以遵循的原则,例如找出所有的名词,画表格排除等等方法。

然后分析实体类之间的关系,是包含,聚合还是依赖,是1:1,还是1:n,还是其他….,根据这些关系,就可以得出实体类和别的实体类想关联的属性,然后再找出每个实体类本身重要的属性。

然后再次分析Use Case的事件流,一方面check实体类的设计是否合理,另一方面你可以找出动词,分析对实体类的控制逻辑,这样就可以可以设计出业务控制类,一般你可以一个实体类一个控制类,也可以业务逻辑相关的实体类由一个Facade Session Bean(非EJB含义)来统一控制,这里面的控制类的颗粒度就由你自己来掌握了。一般来说先可以设计一些细颗粒度的控制类,然后再按照模块,用粗粒度封装细颗粒度的控制类,提供给Web层一个Facade。

然后你可以画序列图,就是用序列图来表达事件流,在这个过程中,你需要不断回到类图,给控制类添加方法,而序列图就是控制类的方法调用。

至此,你已经在Rose里面完成了概要设计,当然你不可能一次设计完善,会有很多次迭代,因此你不能一开始把类设计的太详细,只抓住主要的属性和方法,特别需要注意的是,是抽象的设计,不要用具体的编程语言来表达类。

三。实施(结合xdoclet和Schema工具自动生成代码)

然后你就可以抛开Rose了,转到Eclipse+Togehter里面,根据那些类,规划一下package层次,然后在Together里面进行类的详细设计,所有需要的属性一一写上,当然你还是不可能一下把所有的属性方法写全,不过没有关系,把重要的写好就行了。

然后类框架已经生成好了,给所有的实体类加上xdoclet,然后生成hbm,然后用Hibernate的ExportScheme生成DDL,运行一遍自动创建好所有的表。这样所有的实体相关类全部做好了。

你现在就集中精力把控制类那些方法里面的代码填写上就OK了,在这个过程,你会发现有些实体类缺属性,没有关系,加上属性,然后写好xdoclet,运行一遍,自动生成hbm,自动创建好表,然后继续写你的方法,也有可能你发现控制类缺方法,那么就加上。

基本上实体类就是getter/setter,和少量的实体相关方法,所有的控制逻辑都写在控制类里面。

最后你的软件就基本写好了,用Eclipse生成好一堆你的testCase运行测试,反复修改,除bug。

看看使用OOAD的设计思路,是多么的爽的事情阿!你只需要把精力放到Use Case的抽象,实体类的关系总结,控制类的归纳。而当你使用Eclipse+Together之后,你所需要写的代码只不过是控制类的方法实现代码,其他的都已经生成好了。另外可能需要写少量工具类。

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)

[zt]Effective Java读书笔记

September 11, 2006

终于翻开这本James都称赞的java经典书籍了,发现比一般的英语书籍要难懂一些。但是里面的Item都是非常实用的,是java程序员应该理解的。Creating and Destroying Object

Item 1:考虑用静态工厂方法替代构造器
例如:public static Boolean valueOf(boolean b)
{
return (b?Boolean.TRUE:Boolean.FALSE);
}
这样的好处是方法有名字,并且它可以复用对象,不像构造器每次调用都产生新的对象。其次它还可以返回返回类型的子类。不好的地方是如果没有public or protected构造器的类将不能被继承。还有就是静态工厂方法的名字和其他的静态方法名字不容易区分。

Item 2:通过添加私有构造器来加强单例属性(singletom property)
例如:public class Hello
{
private static final Hello Instance = new Hell();

private Hello()
{}

public static Hello getInstance()
{
return Instance;

}
}
这个私有构造器只能在内部被使用,确保了单例模式!
Item 3:避免创建重复的对象
对不可修改的对象尽量进行复用,这样效率和性能都会提高。例如如果循环100次String s = new String(“hello”)将创建100个对象 循环100次String s = “hello”;则只创建了一个对象。很好的进行了复用。

Item 4:用私有构造器来避免被实例化

例如public UtilityClass
{
private UtilityClass()
{}

///
}
通常那些工具类是这么设计的

Item 5:消除绝对的对象引用

虽然java中使用gc来管理内存,但是如果不注意的话也会产生“内存泄漏”。例如下面的程序
public class Stack
{
private Object[] elements;
private int size = 0;

public Stack(int i)
{
this.elements = new Object[i];
}

public void push(Object e)
{
ensure();
elements[size++] = e;
}

public Object pop()
{
if(size == 0)
{
////
}

return elements[size--];
}

private void ensure()
{
////
}
}
标记的地方存在着内存泄漏的问题,因为当他被弹出栈的时候,它也没有成为可回收的垃圾对象,Stack维护着他们的绝对的引用。将不能更改。改进的方法是如下的写法
public Object pop()
{
if(size == 0)
{
////
}
Object obj = elements[--size];
elements[size] = null;

return obj;
}
但是切忌不要滥用null。

Item 6:避免finalizer
垃圾回收器是低线程级别运行的且不能被强迫执行。System.gc()只是建议垃圾回收器收集垃圾,它可不一定马上运行,而且垃圾回收器运行的时候会挂起其他线程导致程序停止响应。推荐使用的方法类似于
InputStream is = null;

try
{
is = /////;
}
finally
{
is.close();
}

Methods Common to All Objects

item 7:当你覆盖equals方法的时候一定要遵守general contact

   覆盖equals的时候一定要加倍的小心,其实最好的办法就是不覆盖这个方法。比如在下面的情况下就可以不覆盖

   1这个类的每个实例都是唯一的,例如Thread

   2 如果你不关心这个类是否该提供一个测试逻辑相等的方法

   3超类已经覆盖了equals方法,并且它合适子类使用

   4如果这个类是private或者是package-private的,并且你确信他不会被调用

   但是当我们要为这个类提供区分逻辑相等和引用相等的方法的时候,我们就必须要覆盖这个方法了。例如String类,Date类等,覆盖的时候我们一定要遵从general contact,说白了就是一个合同。合同的主要内容是

   1x.equals(x)必须返回true

   2x.equalsy)当且仅当y.equals(x)返回true的时候返回true

   3x.equals(y)返回truey.equals(z)返回true,那么x.equals(z)必须返回true

   4.如果没有任何修改得话那么多次调用x.equals(y)的返回值应该不变

   5.任何时候非空的对象x,x.equals(null)必须返回false

下面是作者的建议如何正确的覆盖equals方法

1.  ==检查是否参数就是这个对象的引用

2.  instanceof判断参数的类型是否正确

3.  把参数转换成合适的类型

4.  比较类的字段是不是匹配

例如:

public boolean equals(Object o)

{

       if(o== this) return true;

       if(!(o instanceof xxxx) return false;

       xxx in = (xxx)o;

       return ……..

}

最后一点要注意的时候不要提供这样的方法public boolean equals(MyClass o)这样是重载并不是覆盖Objectequals方法

item 8 :当你覆盖equals的时候必须覆盖hashCode方法

    这点必须切忌,不然在你和hash-based集合打交道的时候,错误就会出现了。关键问题在于一定要满足相等的对象必须要有相等的hashCode。如果你在PhoneNumber类中覆盖了equals方法,但是没有覆盖hashCode方法,那么当你做如下操作的时候就会出现问题了。

Map m = new HashMap();

m.put(new PhoneNumber(408,863,3334),”ming”)
当你调用m.get(new PhoneNumber(408,863,3334))的时候你希望得到ming但是你却得到了null,为什么呢因为在整个过程中有两个PhoneNumber的实例,一个是put一个是get,但是他们两个逻辑相等的实例却得到不同的hashCode那么怎么可以取得以前存入的ming呢。

Item 9:永远覆盖toString方法

    在ObjecttoString方法返回的形式是Class的类型加上@加上16进制的hashcode。你最好在自己的类中提供toString方法更好的表述实例的信息,不然别人怎么看得明白呢。

Item 10:覆盖clone()方法的时候一定要小心

    一个对象要想被Clone,那么要实现Clone()接口,这个接口没有定义任何的方法,但是如果你不实现这个接口的话,调用clone方法的时候会出现CloneNotSupportedException,这就是作者叫做mixin的接口类型。通常clone()方法可以这样覆盖

public Object clone()

{

try
{

              return super.clone();

}

catch(CloneNotSupportedException e)
{}

}

但是当你要clone的类里面含有可修改的引用字段的时候,那么你一定要把整个类的蓝图进行复制,如果对你clone得到的对象进行修改的时候还会影响到原来的实例,那么这是不可取的。所以应该这样clone()

public Object clone() throws CloneNotSupportedException

{

       Stack Result  = (Stack)super.clone();

       Result.elements = (Object[])elements.clone();

       Return result;

}

其中elementsstack类中可修改的引用字段,注意如果elementsfinal的话我们就无能为力了,因为不能给他重新赋值了.其实如果不是必须的话,根本就不用它最好。

Item 11:考虑适当的时候覆盖Comparable接口

     Thinking in java上说的更清楚,这里不多少了。

    越来越发现这是一本难得的好书,Java程序员不看这本书的话真是很遗憾。本章讲述的是类和接口相关的问题。这几个Item都非常重要.Item 12:把类和成员的可访问范围降到最低

好的模块设计应该尽最大可能封装好自己的内部信息,这样可以把模块之间的耦合程度降到最低。开发得以并行,无疑这将加快开发的速度,便于系统地维护。Java中通过访问控制符来解决这个问题。

  1. public表示这个类在任何范围都可用。
  2. protected表示只有子类和包内的类可以使用
  3. private-package(default)表示在包内可用
  4. private表示只有类内才可以用

你在设计一个类的时候应该尽量的按照4321得顺序设计。如果一个类只是被另一个类使用,那么应该考虑把它设计成这个类的内部类。通常public的类不应该有public得字段,不过我们通常会用一个类来定义所有的常量,这是允许的。不过必须保证这些字段要么是基本数据类型要么引用指向的对象是不可修改的。不然他们将可能被修改。例如下面的定义中data就是不合理的,后面两个没有问题。
public class Con
{
public static final int[] data = {1,2,3};// it is bad
public static final String hello = “world”;
public static final int i = 1;
}

Item 13:不可修改的类更受青睐

不可修改的类意思是他们一经创建就不会改变,例如String类。他们的设计、实现都很方便,安全性高——它们是线程安全的。设计不可修改类有几点规则:

  1. 不要提供任何可以修改对象的方法
  2. 确保没有方法能够被覆盖,可以通过把它声明为final
  3. 所有字段设计成final
  4. 所有字段设计成private
  5. 确保外部不能访问到类的可修改的组件
    不可修改类也有个缺点就是创建不同值得类的时候要创建不同的对象,String就是这样的。通常有个解决的办法就是提供一个帮助类来弥补,例如StringBuffer类。

Item 14:化合(合成)比继承更值得考虑

实现代码重用最重要的办法就是继承,但是继承破坏了封装,导致软件的键壮性不足。如果子类继承了父类,那么它从父类继承的方法就依赖父类的实现,一旦他改变了会导致不可预测的结果。作者介绍了InstrumentedHashSet作为反例进行说明,原因就是没有明白父类的方法实现。作者给出的解决办法是通过化合来代替继承,用包装类和转发方法来解决问题。把想扩展的类作为本类的一个private final得成员变量。把方法参数传递给这个成员变量并得到返回值。这样做的缺点是这样的类不适合回掉框架。继承虽然好,我们却不应该滥用,只有我们能确定它们之间是is-a得关系的时候才使用。

Item 15:如果要用继承那么设计以及文档都要有质量保证,否则就不要用它

为了避免继承带来的问题,你必须提供精确的文档来说明覆盖相关方法可能出现的问题。在构造器内千万不要调用可以被覆盖的方法,因为子类覆盖方法的时候会出现问题。
import java.util.*;

public class SubClass extends SuperClass
{
private final Date date;

public SubClass()
{
date = new Date();
}

public void m()
{
System.out.println(date);
}

public static void main(String[] args)
{
SubClass s = new SubClass();
s.m();
}

}

class SuperClass
{
public SuperClass()
{
m();
}

public void m()
{

}
}
由于在date被初始化之前super()已经被调用了,所以第一次输出null而不是当前的时间。
由于在Clone()或者序列化的时候非常类似构造器的功能,因此readObject()和clone()方法内最好也不要包括能被覆盖的方法。

Item 16:在接口和抽象类之间优先选择前者

接口和抽象类都用来实现多态,不过我们应该优先考虑用接口。知道吗?James说过如果要让他重新设计java的话他会把所有都设计成接口的。抽象类的优点是方便扩展,因为它是被继承的,并且方法可以在抽象类内实现,接口则不行。

Item 17:接口只应该用来定义类型

接口可以这样用的 Collection c = new xxxx();这是我们最常用的。不要把接口用来做其他的事情,比如常量的定义。你应该定义一个类,里面包含public final static 得字段。

Item 18: 在静态和非静态内部类之间选择前者

如果一个类被定义在其他的类内部那么它就是嵌套类,可以分为静态内部类、非静态内部类和匿名类。
static member class 得目的是为enclosing class服务,如果还有其他的目的,就应该把它设计成top-level class。nonstatic member class是和enclosing class instance关联的,如果不需要访问enclosing class instance的话应该把它设计成static得,不然会浪费时间和空间。anonymous class是声明和初始化同时进行的。可以放在代码的任意位置。典型应用是Listener 和process object例如Thread。

    由于以前学过C语言,所以对C还是蛮有感情,而JAVAC又有很多相似之处,很多从C转过来学习JAVA的兄弟,可能一开始都不是很适应,因为很多在C里面的结构在JAVA里面都不能使用了,所以下面我们来介绍一下C语言结构的替代。

     

      Item 19:用类代替结构

      JAVA刚面世的时候,很多C程序员都认为用类来代替结构现在太复杂,代价太大了,但是实际上,如果一个JAVA的类退化到只包含一个数据域的话,这样的类与C语言的结构大致是等价的。

      比方说下面两个程序片段:

      class Point

      {

       private float x;

       private float y;

      }

      实际上这段代码和C语言的结构基本上没什么区别,但是这段代码恐怕是众多OO设计Fans所不齿的,因为它没有体现封装的优异性,没有体现面向对象设计的优点,当一个域被修改的时候,你不可能再采取任何辅助的措施了,那我们再来看一看采用包含私有域和共有访问方法的OO设计代码段:

      class Point

      {

       private float x;

       private float y;

       public Point(float x,float y)

       {

             this.x=x;

             this.y=y;

       }

        public float getX(){retrun x;}

        public float getY(){return y;}

        public void setX(float x){this.x=x;}

        public void setY(float y){this.y=y;}

      }

        单从表面上看,这段代码比上面那个多了很多行,还多了很多函数,但是仔细想一下,这样的OO设计,似乎更人性化,我们可以方面的对值域进行提取,修改等操作,而不直接和值域发生关系,这样的代码不仅让人容易读懂,而且很安全,还吸取了面向对象程序设计的灵活性,试想一下,如果一个共有类暴露它的值域,那么想要在将来的版本中进行修改是impossible的,因为共有类的客户代码已经遍布各处了。

需要提醒一点的是,如果一个类是包级私有的,或者是一个私有的嵌套类,则直接暴露其值域并无不妥之处。

 

Item 20用类层次来代替联合

我们在用C语言来进行开发的时候,经常会用到联合这个概念,比如:

       typedef struct{

     double length;

     double width;    

}rectangleDimensions_t;

那我们在JAVA里面没有联合这个概念,那我们用什么呢?对!用继承,这也是JAVA最吸引我的地方之一,它可以使用更好的机制来定义耽搁数据类型,在Bruce EckelThinking in java里面也多次提到了一个和形状有关的例子,我们可以先笼统的定义一个抽象类,即我们通常所指的超类,每个操作定义一个抽象的方法,其行为取决于标签的值,如果还有其他的操作不依赖于标签的值,则把操作变成根类(继承的类)中的具体方法。

这样做的最重要的优点是:类层次提供了类型的安全性。

其次代码非常明了,这也是OO设计的优点。

而且它很容易扩展,即使是面向多个方面的工作,能够同样胜任。

最后它可以反映这些类型之间本质上的层次关系,从而允许更强的灵活性,以便编译时类型检查。

 

Item 21用类来代替enum结构

Java程序设计语言提出了类型安全枚举的模式来替代enum结构,它的基本思想很简单:定义一个类来代表枚举类型的单个元素,并且不提供任何公有的构造函数,相反,提供公有静态final类,使枚举类型中的每一个常量都对应一个域。

类型安全枚举类型的一个缺点是,装载枚举类的和构造常量对象时,需要一定的时间和空间开销,除非是在资源很受限制的设备比如蜂窝电哈和烤面包机上,否则在实际中这个问题不会被考虑。

 总之,类型安全枚举类型明显优于int类型,除非实在一个枚举类型主要被用做一个集合元素,或者主要用在一个资源非常不受限的环境下,否则类型安全枚举类型的缺点都不成问题,依次,在要求使用一个枚举类型的环境下,我们首先应考虑类型安全枚举类型模式。

 

Item 22用类和接口来代替函数指针

众所周知,JAVA语言和C的最大区别在于,前者去掉了指针,小生第一次接触JAVA的时候觉得好不习惯,因为突然一下子没了指针,觉得好不方面啊,C语言的精髓在于其指针的运用,而JAVA却把它砍掉了,让人好生郁闷,不过随着时间的推移,我渐渐明白了用类和接口的应用也同样可以提供同样的功能,我们可以直接定义一个这样一个类,他的方法是执行其他方法上的操作,如果一个类仅仅是导出这样一个方法,那么它实际上就是一个指向该方法的指针,举个例子:

 class StringLengthComprator{

public int compare(String s1,String s2)

{

return s1.length()-s2.length();

}

}

这个类导出一个带两个字符串的方法,它是一个用于字符串比较的具体策略。它是无状态的,没有域,所以,这个类的所有实例在功能上都是等价的,可以节省不必要的对象创建开销。但是我们不好直接把这个类传递给可户使用,因为可户无法传递任何其他的比较策略。相反,我们可以定义一个接口,即我们在设计具体策略类的时候还需要定义一个策略接口:

      public interface Comparator{

           public int compare(Object o1,Object o2);

}

  我们完全可以依照自己的需要来定义它。

具体的策略类往往使用匿名类声明。

JAVA中,我们为了实现指针的模式,声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类,如果一个具体策略只被使用一次的话,那么通常使用匿名类来声明和实例化这个具体策略类,如果一个策略类反复使用,那么它的类通常是一个私有的的静态成员类。

下面我们来讨论一下有关方法设计的几个方面,下面说的几个要点大多数都是应用在构造函数中,当然也使用于普通方法,我们追求的依然是程序的可用性,健壮性和灵活性。

Item 23检查参数的有效性

非公有的方法我们应该用断言的方法来检查它的参数,而不是使用通常大家所熟悉的检查语句来检测。如果我们使用的开发平台是JDK1.4或者更高级的平台,我们可以使用assert结构;否则我们应该使用一种临时的断言机制。

有些参数在使用过程中是先保存起来,然后在使用的时候再进行调用,构造函数正是这种类型的一种体现,所以我们通常对构造函数参数的有效性检查是非常仔细的。

Item 24需要时使用保护性拷贝

众所周知,JAVA在代码安全性方面较C/C++有显著的提高,缓冲区溢出,数组越界,非法指针等等,我们的JAVA都有一个很完善的机制来进行免疫,但是这并不代表我们不必去考虑JAVA的安全性,即便在安全的语言,如果不采取措施,还是无法使自己与其他类隔开。假设类的客户会尽一切手段来破坏这个类的约束条件,在这样的前提下,你必须从保护性的方面来考虑设计程序。通过大量的程序代码研究我们得出这样的结论:对于构造性函数的每个可变参数进行保护性拷贝是必要的。需要注意的是,保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始的对象。对于“参数类型可以被不可信方子类化”的情况,不要用clone方法来进行参数的保护性拷贝。

对于参数的保护性拷贝并不仅仅在于非可变类,当我们编写一个函数或者一个构造函数的时候,如果它要接受客户提供的对象,允许该对象进入到内部数据结构中,则有必要考虑一下,客户提供的对象是否是可变的,如果是,则要考虑其变化的范围是否在你的程序所能容纳的范围内,如果不是,则要对对象进行保护性拷贝,并且让拷贝之后的对象而不是原始对象进入到数据结构中去。当然最好的解决方法是使用非可变的对象作为你的对象内部足见,这样你就可以不必关心保护性拷贝问题了。):

Item 25谨慎使用设计方法的原型

1谨慎的选择方法的名字:即要注意首先要是易于理解的,其次还要与该包中的其他方法的命名风格相一致,最后当然要注意取一个大众所认可的名字。

2)不要追求提供便利的方法:每一个方法都应该提供其应具备的功能点,对于接口和类来方法不要过多,否则会对学习使用维护等等方面带来许多不必要的麻烦,对于每一个类型所支持的每一个动作,都提供一个功能完全的方法,只有一个方法过于频繁的使用时,才考虑为它提供一个快捷方法。

3)避免过长的参数列表:通常在实践中,我们以三个参数作为最大值,参数越少越好,类型相同的长参数列尤其影响客户的使用,两个方法可以避免过长的参数这样的情况发生,一是把一个方法分解成多个,每一个方法只要求使用这些参数的一个子集;二是创建辅助类,用来保存参数的聚集,这些辅助类的状态通常是静态的。

对于参数类型,优先使用接口而不是类。

这样做的目的是避免影响效能的拷贝操作。

谨慎的使用函数对象。

创建函数对象最容易的方法莫过于使用匿名类,但是那样会带来语法上混乱,并且与内联的控制结构相比,这样也会导致功能上的局限性。

Item 26谨慎的使用重载

到底是什么造成了重载机制的混淆算法,这是个争论的话题,一个安全而保守的方法是,永远不要导出两个具有相同参数数目的重载方法。而对于构造函数来说,一个类的多个构造函数总是重载的,在某些情况下,我们可以选择静态工厂,但是对于构造函数来说这样做并不总是切合实际的。

当涉及到构造函数时,遵循这条建议也许是不可能的,但我们应该极力避免下面的情形:

同一组参数只需要经过类型的转换就可以传递给不同的重载方法。如果这样做也不能避免的话,我们至少要保证一点:当传递同样的参数时,所有的重载方法行为一致。如果不能做到这一点,程序员就不能有效的使用方法或者构造函数。

Item 27返回零长度的数组而不是null

因为这样做的原因是编写客户程序的程序员可能忘记写这种专门的代码来处理null返回值。没有理由从一个取数组值的方法中返回null,而不是返回一个零长度数组。

Item 28为所有导出的API元素编写文档注释

不爱写注释可能是大多数程序员新手的通病(包括偶哈~),但是如果想要一个API真正可用,就必须写一个文档来说明它,保持代码和文档的同步是一件比较烦琐的事情,JAVA语言环境提供了javadoc工具,从而使这个烦琐的过程变得容易,这个工具可以根据源代码自动产生API文档。

为了正确得编写API文档,我们必须每一个被导出的类,接口,构造函数,方法和域声明之前加一个文档注释。

每一个方法的文档注释应该见解的描述它和客户之间的约定。

我们接下来讨论一下Java语言的细节,包括局部变量的处理,库的使用,以及两种不是语言本身提供的机制的使用等等一些大家平时可能忽略的问题。

 

Item 29:将局部变量的作用域最小化

C语言要求局部变量必须被生命在代码的开始处相比,Java程序设计语言宽松得多,它允许你在代码的任何位置声明。要想使一个局部变量的作用域最小化,最高小的技术是在第一次需要使用它的地方声明,变量的作用域是从声明它的地方开始到这个声明做在的代码块的结束位止,如果我们把变量的声明和代码的使用位置分开的过大,那么对于读这段代码的人来说,是很不幸的。

我们几乎都是在一个局部变量声明的地方同时给它初始化,注意这是很重要的,甚至有时候,如果我们的初始化应该推迟到下一个代码的位置,我们同时应该把声明也往后延迟。这条规则唯一的例外是try-catch这个语句,因为如果一个变量被方法初始化,那么这个方法很有可能抛出一个异常,那我们最常用的方法就是把它置于try块的内部去进行初始化。由此我们可以得出,for循环优于while循环,我们在能使用for循环的地方尽量使用for而不使用while,因为for循环是完全独立的,所以重用循环变量名字不会有任何伤害。

最后我们要记住的是尽量把我们的函数写的小而集中,这样才能真正组做到最小化局部变量的作用域这一要旨。

Item 30:了解和使用库

使用标准库,我们可以充分利用编写这些库的Java专家的知识,以及在你之前其他人的使用经验,这就是所谓站在巨人的肩膀上看世界吧~

在每一个Java平台的发行版本里面,都会有许多新的包的加入,和这些更新保持一直是值得的,比如说我们J2ME的开发,在MIDP 1.0的时代,我们要写个Game还要自己动手写工具类,现在MIDP2.0推出之后,大多数写游戏的人都觉得方便了很多,因为在这个版本里面加入了游戏包,为我们的开发节省了大量的人力物力。

     Item 31:如果想要知道精确的答案,就要避免使用doublefloat

     对于金融行业来说,对数据的严整性要求是很高的,不容半点马虎,那大家都知道再我们的Java语言里面有两个浮点数类型的变量floatdouble,可能大家会认为他们的精度对于金融行业这样对数字敏感的行业来说,已经够用了,但是在开发当中,我们要尽量少使用doublefloat,因为让他们精确的表达0.1是不可能的。那我们如何解决这个问题呢,答案是使用BigDecimal,int或者long进行货币计算。在这里对大家的忠告是:对于商务运算,我们尽量使用BigDecimal,对于性能要求较高的地方,我们有能力自己处理十进制的小数点,数值不太大的时候,我们可以使用int或者long,根据自己的需要来判定具体使用哪一个,如果范围超过了18位数,那我们必须使用BigDecimal

     Item 32:如果其他类型更适合,则尽量避免使用字符串

     在偶看到这条建议之前,我就很喜欢用字符串,不管在什么场合下,先String了再说,但是实际上很多情况下,我们要根据实际情况来判定到底使用什么类型,而且字符串不适合替代枚举类型,类型安全枚举类型和int值都比字符串更适合用来表示枚举类型的常量。字符串也不适合替代聚集类型,有一个更好的方法就是简单的写一个类来描述这个数据集,通常是一个私有的静态成员类最好。字符串也不适合代替能力表,总而言之,如果可以适合更加适合的数据类型,或者可以编写更加适当的数据类型,那么应该避免使用字符串来表示对象。

Item 33:了解字符串的连接功能

我们经常在使用System.out.println()的时候,往括号里写一串用“+”连接起来的字符串,这是我们最常见的,但是这个方法并不适合规模较大的情形,为连接N个字符串而重复地使用字符串连接操作符,要求N的平方级的时间,这是因为字符串是非可变的,这就导致了在字符串进行连接的时候,前后两者都要拷贝,这个时候我们就提倡使用StingBuffer替代String

Item 34:通过接口引用对象

通俗的说就是尽量优先使用接口而不是类来引用对象,如果有合适的接口存在那么对使用参数,返回值,变量域都应该使用接口类型养成使用接口作为对象的习惯,会使程序变得更加灵活。

如果没有合适的接口,那么,用类而不是接口来引用一个对象,是完全合适的。

Item 35:接口优先于映像机制

java.lang.relect提供了“通过程序来访问关于已装载的类的信息”,由此,我们可以通过一个给定的Class实例,获得Constructor,MethodField实例。

映像机制允许一个类使用另一个类,即使当前编译的时候后者还不存在,但是这种能力也要付出代价:

我们损失了了编译时类型检查的好处,而且要求执行映像访问的代码非常笨拙和冗长,并且在性能上大大损失。

通常,普通应用在运行时刻不应以映像方式访问对象。

Item 36:谨慎的使用本地方法

JNI允许Java应用程序调用本地方法,所谓本地方法是指用本地程序设计语言(如CC++)来编写的特殊方法,本地方法可以在本地语言执行任何计算任务,然后返回到Java程序设计语言中。但是随着JDK1.3及后续版本的推出这种通过使用本地方法来提高性能的方法已不值得提倡,因为现在的JVM越来越快了,而且使用本地方法有一些严重的缺点,比如使Java原本引以为傲的安全性荡然无存,总之在使用本地方法的时候要三思。

Item 37:谨慎使用优化

不要因为性能而牺牲合理的代码结构,努力编写好的程序而不是快的程序,但是避免那些限制性能的设计决定,同时考虑自己设计的API决定的性能后果,为了获得更好的性能而对API进行修改这也是一个非常不好的想法,通常我们在做优化之后,都应该对优化的程度进行一些测量。

Item 38:遵守普遍接受的命名惯例

Java有一套比较完善的命名惯例机制,大部分包含在《The Java Language Specification》,严格得讲这些惯例分成两类,字面的和语法的。

字面涉及包,类,接口,方法和域,语法的命名惯例比较灵活,所以争议更大,字面惯例是非常直接和明确的,而语法惯例则相对复杂,也很松散。但是有一个公认的做法是:“如果长期养成的习惯用法与此不同的话,请不要盲目遵从

Item 12:把类和成员的可访问范围降到最低

好的模块设计应该尽最大可能封装好自己的内部信息,这样可以把模块之间的耦合程度降到最低。开发得以并行,无疑这将加快开发的速度,便于系统地维护。Java中通过访问控制符来解决这个问题。

  1. public表示这个类在任何范围都可用。
  2. protected表示只有子类和包内的类可以使用
  3. private-package(default)表示在包内可用
  4. private表示只有类内才可以用

你在设计一个类的时候应该尽量的按照4321得顺序设计。如果一个类只是被另一个类使用,那么应该考虑把它设计成这个类的内部类。通常public的类不应该有public得字段,不过我们通常会用一个类来定义所有的常量,这是允许的。不过必须保证这些字段要么是基本数据类型要么引用指向的对象是不可修改的。不然他们将可能被修改。例如下面的定义中data就是不合理的,后面两个没有问题。
public class Con
{
public static final int[] data = {1,2,3};// it is bad
public static final String hello = “world”;
public static final int i = 1;
}

Item 13:不可修改的类更受青睐

不可修改的类意思是他们一经创建就不会改变,例如String类。他们的设计、实现都很方便,安全性高——它们是线程安全的。设计不可修改类有几点规则:

  1. 不要提供任何可以修改对象的方法
  2. 确保没有方法能够被覆盖,可以通过把它声明为final
  3. 所有字段设计成final
  4. 所有字段设计成private
  5. 确保外部不能访问到类的可修改的组件
    不可修改类也有个缺点就是创建不同值得类的时候要创建不同的对象,String就是这样的。通常有个解决的办法就是提供一个帮助类来弥补,例如StringBuffer类。

Item 14:化合(合成)比继承更值得考虑

实现代码重用最重要的办法就是继承,但是继承破坏了封装,导致软件的键壮性不足。如果子类继承了父类,那么它从父类继承的方法就依赖父类的实现,一旦他改变了会导致不可预测的结果。作者介绍了InstrumentedHashSet作为反例进行说明,原因就是没有明白父类的方法实现。作者给出的解决办法是通过化合来代替继承,用包装类和转发方法来解决问题。把想扩展的类作为本类的一个private final得成员变量。把方法参数传递给这个成员变量并得到返回值。这样做的缺点是这样的类不适合回掉框架。继承虽然好,我们却不应该滥用,只有我们能确定它们之间是is-a得关系的时候才使用。

Item 15:如果要用继承那么设计以及文档都要有质量保证,否则就不要用它

为了避免继承带来的问题,你必须提供精确的文档来说明覆盖相关方法可能出现的问题。在构造器内千万不要调用可以被覆盖的方法,因为子类覆盖方法的时候会出现问题。
import java.util.*;

public class SubClass extends SuperClass
{
private final Date date;

public SubClass()
{
date = new Date();
}

public void m()
{
System.out.println(date);
}

public static void main(String[] args)
{
SubClass s = new SubClass();
s.m();
}

}

class SuperClass
{
public SuperClass()
{
m();
}

public void m()
{

}
}
由于在date被初始化之前super()已经被调用了,所以第一次输出null而不是当前的时间。
由于在Clone()或者序列化的时候非常类似构造器的功能,因此readObject()和clone()方法内最好也不要包括能被覆盖的方法。

Item 16:在接口和抽象类之间优先选择前者

接口和抽象类都用来实现多态,不过我们应该优先考虑用接口。知道吗?James说过如果要让他重新设计java的话他会把所有都设计成接口的。抽象类的优点是方便扩展,因为它是被继承的,并且方法可以在抽象类内实现,接口则不行。

Item 17:接口只应该用来定义类型

接口可以这样用的 Collection c = new xxxx();这是我们最常用的。不要把接口用来做其他的事情,比如常量的定义。你应该定义一个类,里面包含public final static 得字段。

Item 18: 在静态和非静态内部类之间选择前者

如果一个类被定义在其他的类内部那么它就是嵌套类,可以分为静态内部类、非静态内部类和匿名类。
static member class 得目的是为enclosing class服务,如果还有其他的目的,就应该把它设计成top-level class。nonstatic member class是和enclosing class instance关联的,如果不需要访问enclosing class instance的话应该把它设计成static得,不然会浪费时间和空间。anonymous class是声明和初始化同时进行的。可以放在代码的任意位置。典型应用是Listener 和process object例如Thread。

    由于以前学过C语言,所以对C还是蛮有感情,而JAVAC又有很多相似之处,很多从C转过来学习JAVA的兄弟,可能一开始都不是很适应,因为很多在C里面的结构在JAVA里面都不能使用了,所以下面我们来介绍一下C语言结构的替代。

     

      Item 19:用类代替结构

      JAVA刚面世的时候,很多C程序员都认为用类来代替结构现在太复杂,代价太大了,但是实际上,如果一个JAVA的类退化到只包含一个数据域的话,这样的类与C语言的结构大致是等价的。

      比方说下面两个程序片段:

      class Point

      {

       private float x;

       private float y;

      }

      实际上这段代码和C语言的结构基本上没什么区别,但是这段代码恐怕是众多OO设计Fans所不齿的,因为它没有体现封装的优异性,没有体现面向对象设计的优点,当一个域被修改的时候,你不可能再采取任何辅助的措施了,那我们再来看一看采用包含私有域和共有访问方法的OO设计代码段:

      class Point

      {

       private float x;

       private float y;

       public Point(float x,float y)

       {

             this.x=x;

             this.y=y;

       }

        public float getX(){retrun x;}

        public float getY(){return y;}

        public void setX(float x){this.x=x;}

        public void setY(float y){this.y=y;}

      }

        单从表面上看,这段代码比上面那个多了很多行,还多了很多函数,但是仔细想一下,这样的OO设计,似乎更人性化,我们可以方面的对值域进行提取,修改等操作,而不直接和值域发生关系,这样的代码不仅让人容易读懂,而且很安全,还吸取了面向对象程序设计的灵活性,试想一下,如果一个共有类暴露它的值域,那么想要在将来的版本中进行修改是impossible的,因为共有类的客户代码已经遍布各处了。

需要提醒一点的是,如果一个类是包级私有的,或者是一个私有的嵌套类,则直接暴露其值域并无不妥之处。

 

Item 20用类层次来代替联合

我们在用C语言来进行开发的时候,经常会用到联合这个概念,比如:

       typedef struct{

     double length;

     double width;    

}rectangleDimensions_t;

那我们在JAVA里面没有联合这个概念,那我们用什么呢?对!用继承,这也是JAVA最吸引我的地方之一,它可以使用更好的机制来定义耽搁数据类型,在Bruce EckelThinking in java里面也多次提到了一个和形状有关的例子,我们可以先笼统的定义一个抽象类,即我们通常所指的超类,每个操作定义一个抽象的方法,其行为取决于标签的值,如果还有其他的操作不依赖于标签的值,则把操作变成根类(继承的类)中的具体方法。

这样做的最重要的优点是:类层次提供了类型的安全性。

其次代码非常明了,这也是OO设计的优点。

而且它很容易扩展,即使是面向多个方面的工作,能够同样胜任。

最后它可以反映这些类型之间本质上的层次关系,从而允许更强的灵活性,以便编译时类型检查。

 

Item 21用类来代替enum结构

Java程序设计语言提出了类型安全枚举的模式来替代enum结构,它的基本思想很简单:定义一个类来代表枚举类型的单个元素,并且不提供任何公有的构造函数,相反,提供公有静态final类,使枚举类型中的每一个常量都对应一个域。

类型安全枚举类型的一个缺点是,装载枚举类的和构造常量对象时,需要一定的时间和空间开销,除非是在资源很受限制的设备比如蜂窝电哈和烤面包机上,否则在实际中这个问题不会被考虑。

 总之,类型安全枚举类型明显优于int类型,除非实在一个枚举类型主要被用做一个集合元素,或者主要用在一个资源非常不受限的环境下,否则类型安全枚举类型的缺点都不成问题,依次,在要求使用一个枚举类型的环境下,我们首先应考虑类型安全枚举类型模式。

 

Item 22用类和接口来代替函数指针

众所周知,JAVA语言和C的最大区别在于,前者去掉了指针,小生第一次接触JAVA的时候觉得好不习惯,因为突然一下子没了指针,觉得好不方面啊,C语言的精髓在于其指针的运用,而JAVA却把它砍掉了,让人好生郁闷,不过随着时间的推移,我渐渐明白了用类和接口的应用也同样可以提供同样的功能,我们可以直接定义一个这样一个类,他的方法是执行其他方法上的操作,如果一个类仅仅是导出这样一个方法,那么它实际上就是一个指向该方法的指针,举个例子:

 class StringLengthComprator{

public int compare(String s1,String s2)

{

return s1.length()-s2.length();

}

}

这个类导出一个带两个字符串的方法,它是一个用于字符串比较的具体策略。它是无状态的,没有域,所以,这个类的所有实例在功能上都是等价的,可以节省不必要的对象创建开销。但是我们不好直接把这个类传递给可户使用,因为可户无法传递任何其他的比较策略。相反,我们可以定义一个接口,即我们在设计具体策略类的时候还需要定义一个策略接口:

      public interface Comparator{

           public int compare(Object o1,Object o2);

}

  我们完全可以依照自己的需要来定义它。

具体的策略类往往使用匿名类声明。

JAVA中,我们为了实现指针的模式,声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类,如果一个具体策略只被使用一次的话,那么通常使用匿名类来声明和实例化这个具体策略类,如果一个策略类反复使用,那么它的类通常是一个私有的的静态成员类。

下面我们来讨论一下有关方法设计的几个方面,下面说的几个要点大多数都是应用在构造函数中,当然也使用于普通方法,我们追求的依然是程序的可用性,健壮性和灵活性。

Item 23检查参数的有效性

非公有的方法我们应该用断言的方法来检查它的参数,而不是使用通常大家所熟悉的检查语句来检测。如果我们使用的开发平台是JDK1.4或者更高级的平台,我们可以使用assert结构;否则我们应该使用一种临时的断言机制。

有些参数在使用过程中是先保存起来,然后在使用的时候再进行调用,构造函数正是这种类型的一种体现,所以我们通常对构造函数参数的有效性检查是非常仔细的。

Item 24需要时使用保护性拷贝

众所周知,JAVA在代码安全性方面较C/C++有显著的提高,缓冲区溢出,数组越界,非法指针等等,我们的JAVA都有一个很完善的机制来进行免疫,但是这并不代表我们不必去考虑JAVA的安全性,即便在安全的语言,如果不采取措施,还是无法使自己与其他类隔开。假设类的客户会尽一切手段来破坏这个类的约束条件,在这样的前提下,你必须从保护性的方面来考虑设计程序。通过大量的程序代码研究我们得出这样的结论:对于构造性函数的每个可变参数进行保护性拷贝是必要的。需要注意的是,保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始的对象。对于“参数类型可以被不可信方子类化”的情况,不要用clone方法来进行参数的保护性拷贝。

对于参数的保护性拷贝并不仅仅在于非可变类,当我们编写一个函数或者一个构造函数的时候,如果它要接受客户提供的对象,允许该对象进入到内部数据结构中,则有必要考虑一下,客户提供的对象是否是可变的,如果是,则要考虑其变化的范围是否在你的程序所能容纳的范围内,如果不是,则要对对象进行保护性拷贝,并且让拷贝之后的对象而不是原始对象进入到数据结构中去。当然最好的解决方法是使用非可变的对象作为你的对象内部足见,这样你就可以不必关心保护性拷贝问题了。):

Item 25谨慎使用设计方法的原型

1谨慎的选择方法的名字:即要注意首先要是易于理解的,其次还要与该包中的其他方法的命名风格相一致,最后当然要注意取一个大众所认可的名字。

2)不要追求提供便利的方法:每一个方法都应该提供其应具备的功能点,对于接口和类来方法不要过多,否则会对学习使用维护等等方面带来许多不必要的麻烦,对于每一个类型所支持的每一个动作,都提供一个功能完全的方法,只有一个方法过于频繁的使用时,才考虑为它提供一个快捷方法。

3)避免过长的参数列表:通常在实践中,我们以三个参数作为最大值,参数越少越好,类型相同的长参数列尤其影响客户的使用,两个方法可以避免过长的参数这样的情况发生,一是把一个方法分解成多个,每一个方法只要求使用这些参数的一个子集;二是创建辅助类,用来保存参数的聚集,这些辅助类的状态通常是静态的。

对于参数类型,优先使用接口而不是类。

这样做的目的是避免影响效能的拷贝操作。

谨慎的使用函数对象。

创建函数对象最容易的方法莫过于使用匿名类,但是那样会带来语法上混乱,并且与内联的控制结构相比,这样也会导致功能上的局限性。

Item 26谨慎的使用重载

到底是什么造成了重载机制的混淆算法,这是个争论的话题,一个安全而保守的方法是,永远不要导出两个具有相同参数数目的重载方法。而对于构造函数来说,一个类的多个构造函数总是重载的,在某些情况下,我们可以选择静态工厂,但是对于构造函数来说这样做并不总是切合实际的。

当涉及到构造函数时,遵循这条建议也许是不可能的,但我们应该极力避免下面的情形:

同一组参数只需要经过类型的转换就可以传递给不同的重载方法。如果这样做也不能避免的话,我们至少要保证一点:当传递同样的参数时,所有的重载方法行为一致。如果不能做到这一点,程序员就不能有效的使用方法或者构造函数。

Item 27返回零长度的数组而不是null

因为这样做的原因是编写客户程序的程序员可能忘记写这种专门的代码来处理null返回值。没有理由从一个取数组值的方法中返回null,而不是返回一个零长度数组。

Item 28为所有导出的API元素编写文档注释

不爱写注释可能是大多数程序员新手的通病(包括偶哈~),但是如果想要一个API真正可用,就必须写一个文档来说明它,保持代码和文档的同步是一件比较烦琐的事情,JAVA语言环境提供了javadoc工具,从而使这个烦琐的过程变得容易,这个工具可以根据源代码自动产生API文档。

为了正确得编写API文档,我们必须每一个被导出的类,接口,构造函数,方法和域声明之前加一个文档注释。

每一个方法的文档注释应该见解的描述它和客户之间的约定。

我们接下来讨论一下Java语言的细节,包括局部变量的处理,库的使用,以及两种不是语言本身提供的机制的使用等等一些大家平时可能忽略的问题。

 

Item 29:将局部变量的作用域最小化

C语言要求局部变量必须被生命在代码的开始处相比,Java程序设计语言宽松得多,它允许你在代码的任何位置声明。要想使一个局部变量的作用域最小化,最高小的技术是在第一次需要使用它的地方声明,变量的作用域是从声明它的地方开始到这个声明做在的代码块的结束位止,如果我们把变量的声明和代码的使用位置分开的过大,那么对于读这段代码的人来说,是很不幸的。

我们几乎都是在一个局部变量声明的地方同时给它初始化,注意这是很重要的,甚至有时候,如果我们的初始化应该推迟到下一个代码的位置,我们同时应该把声明也往后延迟。这条规则唯一的例外是try-catch这个语句,因为如果一个变量被方法初始化,那么这个方法很有可能抛出一个异常,那我们最常用的方法就是把它置于try块的内部去进行初始化。由此我们可以得出,for循环优于while循环,我们在能使用for循环的地方尽量使用for而不使用while,因为for循环是完全独立的,所以重用循环变量名字不会有任何伤害。

最后我们要记住的是尽量把我们的函数写的小而集中,这样才能真正组做到最小化局部变量的作用域这一要旨。

Item 30:了解和使用库

使用标准库,我们可以充分利用编写这些库的Java专家的知识,以及在你之前其他人的使用经验,这就是所谓站在巨人的肩膀上看世界吧~

在每一个Java平台的发行版本里面,都会有许多新的包的加入,和这些更新保持一直是值得的,比如说我们J2ME的开发,在MIDP 1.0的时代,我们要写个Game还要自己动手写工具类,现在MIDP2.0推出之后,大多数写游戏的人都觉得方便了很多,因为在这个版本里面加入了游戏包,为我们的开发节省了大量的人力物力。

     Item 31:如果想要知道精确的答案,就要避免使用doublefloat

     对于金融行业来说,对数据的严整性要求是很高的,不容半点马虎,那大家都知道再我们的Java语言里面有两个浮点数类型的变量floatdouble,可能大家会认为他们的精度对于金融行业这样对数字敏感的行业来说,已经够用了,但是在开发当中,我们要尽量少使用doublefloat,因为让他们精确的表达0.1是不可能的。那我们如何解决这个问题呢,答案是使用BigDecimal,int或者long进行货币计算。在这里对大家的忠告是:对于商务运算,我们尽量使用BigDecimal,对于性能要求较高的地方,我们有能力自己处理十进制的小数点,数值不太大的时候,我们可以使用int或者long,根据自己的需要来判定具体使用哪一个,如果范围超过了18位数,那我们必须使用BigDecimal

     Item 32:如果其他类型更适合,则尽量避免使用字符串

     在偶看到这条建议之前,我就很喜欢用字符串,不管在什么场合下,先String了再说,但是实际上很多情况下,我们要根据实际情况来判定到底使用什么类型,而且字符串不适合替代枚举类型,类型安全枚举类型和int值都比字符串更适合用来表示枚举类型的常量。字符串也不适合替代聚集类型,有一个更好的方法就是简单的写一个类来描述这个数据集,通常是一个私有的静态成员类最好。字符串也不适合代替能力表,总而言之,如果可以适合更加适合的数据类型,或者可以编写更加适当的数据类型,那么应该避免使用字符串来表示对象。

Item 33:了解字符串的连接功能

我们经常在使用System.out.println()的时候,往括号里写一串用“+”连接起来的字符串,这是我们最常见的,但是这个方法并不适合规模较大的情形,为连接N个字符串而重复地使用字符串连接操作符,要求N的平方级的时间,这是因为字符串是非可变的,这就导致了在字符串进行连接的时候,前后两者都要拷贝,这个时候我们就提倡使用StingBuffer替代String

Item 34:通过接口引用对象

通俗的说就是尽量优先使用接口而不是类来引用对象,如果有合适的接口存在那么对使用参数,返回值,变量域都应该使用接口类型养成使用接口作为对象的习惯,会使程序变得更加灵活。

如果没有合适的接口,那么,用类而不是接口来引用一个对象,是完全合适的。

Item 35:接口优先于映像机制

java.lang.relect提供了“通过程序来访问关于已装载的类的信息”,由此,我们可以通过一个给定的Class实例,获得Constructor,MethodField实例。

映像机制允许一个类使用另一个类,即使当前编译的时候后者还不存在,但是这种能力也要付出代价:

我们损失了了编译时类型检查的好处,而且要求执行映像访问的代码非常笨拙和冗长,并且在性能上大大损失。

通常,普通应用在运行时刻不应以映像方式访问对象。

Item 36:谨慎的使用本地方法

JNI允许Java应用程序调用本地方法,所谓本地方法是指用本地程序设计语言(如CC++)来编写的特殊方法,本地方法可以在本地语言执行任何计算任务,然后返回到Java程序设计语言中。但是随着JDK1.3及后续版本的推出这种通过使用本地方法来提高性能的方法已不值得提倡,因为现在的JVM越来越快了,而且使用本地方法有一些严重的缺点,比如使Java原本引以为傲的安全性荡然无存,总之在使用本地方法的时候要三思。

Item 37:谨慎使用优化

不要因为性能而牺牲合理的代码结构,努力编写好的程序而不是快的程序,但是避免那些限制性能的设计决定,同时考虑自己设计的API决定的性能后果,为了获得更好的性能而对API进行修改这也是一个非常不好的想法,通常我们在做优化之后,都应该对优化的程度进行一些测量。

Item 38:遵守普遍接受的命名惯例

Java有一套比较完善的命名惯例机制,大部分包含在《The Java Language Specification》,严格得讲这些惯例分成两类,字面的和语法的。

字面涉及包,类,接口,方法和域,语法的命名惯例比较灵活,所以争议更大,字面惯例是非常直接和明确的,而语法惯例则相对复杂,也很松散。但是有一个公认的做法是:“如果长期养成的习惯用法与此不同的话,请不要盲目遵从

VN:F [1.6.3_896]
Rating: 0.0/10 (0 votes cast)
VN:F [1.6.3_896]
Rating: 0 (from 0 votes)