`

Java面向对象设计最佳实践 - 方法设计(一)

阅读更多

在《类的设计基础知识》文章中提到, 方法作为 命名的控制类或者对象的有状态或无状态的执行模块,可称作类或者对象的“行为”。 方法在类中的地位相当重要,利用面向对象的多态性能够设计更为优雅的结构,同时巧妙地使用继承能够更好地减少冗余,因此其设计是重重之重。方 法的设计实践会按照下列的议程安排:

  • 名称和注释设计
    • 注释即规约
    • 名称即语义
  • 范围设计
    • 物理范围
    • 逻辑范围
  • 参数设计
    • 参数类型
    • 参数顺序
    • 参数数量
  • 返回值设计
    • 返回值类型
    • 返回值特征
  • 异常设计
    • 异常类型
    • 异常转换
  • 泛型设计
  • AOP设计


一、 名称和注释设计


方法名称好比人的姓名,父母在给自己子女起名的时候,在字面上,尽量不想重复,同时在意义上,面体现了 父母的美好希望。好的名称不仅容易让人记住,同时会想起其特征。方法也是如此。


在面向对象设计中,对于API 方法来说,说明它的语义和体现的行为。那么,设计人员应该遵循方法的设计原则: 输入和输出,以及异常触发应该是规约的和可控制的,也就说方法说体现的行为或者履行的职责应该是有限的 。凡是任意暴露(从结构上)和职责(从逻辑上)模糊或者歧义,皆视作低内聚- 高耦合设计。


那么,规约方法行为和科学的名称是必要,也是重要的。


       1.  注释即规约

TDD 中,有句名言是“实现未动,测试先行”。那么设计中,则是“实现未动,规约先行”。也就说,在实现和命名方法的之前,应该好好设计下这个方法 的语义规约,即它要表达的语义和行为是什么。也叫做 契约编程 。良好的单一性规约,是 高内聚- 低耦合 的前提。


在实现方法之前,一般的设计人员更多地关注与功能性需求,而非功能性需求更多地由架构师考虑。显然,良 好的设计是需要全面考虑到的。下面总结了一些通用的规约,这也是后续文章重点介绍的。

功能性需求的规约:

  • 方法物理上和逻辑上的范围  
  • 参数的接受范围、其数量、顺序、类型以及意义
  • 返回值、其类型以及意义
  • 方法异常触发的条件以及意义
  • 前提和后续条件


非 功能性需求的规约:

  • 性能指标(比如,时间和空间)
  • 安全(比如,执行权限、防止攻击)
  • 为一致性(比如,什么条件下保持一致性)
  • 软件条件限制(比如,编程语言限制)

当了解这些规约之后,那么剩下就是怎么体现到设计中。代码实现是利用编程语言,而规约表述是自然语言, 自然地,在编程语言中,大多数是利用注释来表达。Java 同样如此,不过可利用 Javadoc 来生成 API 文档。


当客户端使用方法之前,它可以通过注释来了解方法规约,更好地增加了方法的可用性。


在方法实现之前,给方法添加注释,固然是一种很好的方法。当受制于时间的情况下,采用一种“命名即注 释”的方法更佳。接下来接受方法命名。


       2.  名称即语义

前面提到“命名即注释”的方法,通过命名就知道方法的意图,一般用于方法实现相对简单的情 况,比如在Java Bean 中, Setter Getter 方法就是一种体现。设计时,可以利用这种方法提高方法的易用性和可读性。


Java API 中,提供了很多命名模式的参考,其命名一般可以归纳为几类:

a)  动词

以动词命名方法,比如:

i.  对象状态比较

java.lang.Object#equals(java.lang.Object),
java.lang.String#contains(java.lang. CharSequence),
java.util.Collection<E>#contains(E),
java.util.Comparator<T>#compare(T,T)

 

ii. 状态改变的

java.lang.Collection<E>#clear(),
java.io.Closeable#close(),
java.lang.String#intern(),
java.lang.String#trim();

 

b)  动词- 名词

i.  状态转化

java.lang.Integer#parseInt(java.lang.String) ,
java.lang.String#getBytes(),
javax.jms.Session#createMessage()

 

ii.  条件判断

java.util.Iterator<E>#hasNext(),
java.lang.Class#isInterface(),
java.lang.String#isEmpty(),
java.lang.Thread# holdsLock (java.lang.Object)

 

iii.  状态改变

java.lang.Thread#setContextClassLoader(java.lang.ClassLoader),
javax.Servlet.http.HttpSession#removeAttribute(java.lang.String)

 

iv.  通讯传输

javax.mail.Transport#sendMessage(javax.mail.Message,javax.mail.Address[])

 


c)  动词- 介词

i.  对象状态比较

java.lang.Comparable<E>#compareTo(E)

 


ii.  抽象实现

java.util.Formattable#formatTo(java.util.Formatter,int,int)

 


d)  动词- 介词 - 名词

e)  名词

i.  状态转化

java.util.Map<K,V>#values(),
java.util.Collection<E>#iterator(),
java.lang.Number#longValue(),
java.util.Collections#singletonMap(K,V)

 


ii.  用户状态表示

java.lang.String#length(),
java.lang.Object#hashCode(),
java.util.Collection#size()

 


f)  名词- 动词过去式

i.  事件回调


java.awt.ActionListener#actionPerformed(java.awt.ActionEvent),
javax.servlet.ServletContextListener# contextInitialized javax.servlet.ServletContextEvent)

 

g)  介词- 名词

以介词- 名词组合的方法,

i.  状态转换

java.lang.Object#toString(),
java.lang.String#toLowerCase(),
java.util.Collections#asList(T...)

 

ii.  事件回调

javax.jms.MessageListener#onMessage(javax.jms.Message)

 

h)  形容词- 名词

i.  对象创建


javax.xml.parsers.DocumentBuilderFactory#newDocumentBuilder()


ii.  状态转换


java.util.Collections#synchronizedList(java.util.List<E>),
java.util.Collections#unmodifiableList(java.util.List<? extends E>)

 

iii.  状态表示。

java.util.concurrent.BlockingQueue#remainingCapacity()

 

 

通过归纳的这些命名模式,相信能够给方法命名带来些灵感。


(待续)

分享到:
评论
13 楼 cnsilent 2010-05-19  
呵呵,明天刚好要学对象,先收了~! 楼主归纳的真仔细
12 楼 Lance0512 2010-05-19  
以前从来没有关注过命名问题 ,看了LZ 的贴子 深受启发。。
11 楼 mercyblitz 2010-05-18  
H_eaven 写道
借个地方总结下:
//方法设计的目标,好的设计与差的设计的表现,
//良好方法设计的做法,
//关键字:清晰性,重用性,封装。
//方法设计的讨论: 暂时分两个角度,一种是抽象含义上的,一种是物理结构上的,分这两种的出发点物理上的很容易理解,能够被一定的量化,而抽象含义上的比较范范,它需用根据环境做出具体的处理,
//一个方法中的代码抽象级别应该一致,这与一个类暴露给外部的接口方法的抽象程度相一致是一样的。
//比如现再有个方法要完成生成一个实例,生成实例需要分三个步骤完成,第一步实例化,第二步配置,第三步初始化,如果把这三个步骤的实现代码都写入到一个方法中,直观的是得到一个长长的方法,如果想要知道生成一个对象实例的步骤则必须把这个完整的代码看完,才能明白它表达的意思,
//而如果将这三步封装到方法中,则清晰的明白生成一个实例的步骤,一旦封装到方法中,则重用性也成为可能,因为变量表达式仅在方法内可见,而方法则在类内可见,如果通用性更强,则可以进一步放宽访问权限。
//针对这个角度的讨论好消息是我们知道目标了,坏消息是我们缺乏一些可以量化的标准。
   //一个方法的物理构件:方法头,方法体;
   //方法头包括:修饰符,返回值,方法名,参数列表(参数类型,数量,顺序),异常列表,
   //修饰符仅讨论访问修饰符,其它的不讨论,访问权限尽可能的底以保证封装性,私有方法对于类来说是类的实现代码,而不是类的API,这将使类的使用更简单,也将使以后修改类以保证二进制兼容性更容易,
   //返回值类型与参数类型尽可能使用抽象类型,这是面向对象设计基本原则依赖倒转(依赖抽象)所要求的,Java5支持协变返回类型,所以现再的使用方式应该是在定义抽象方法中使用抽象类型,而在实现方法中使用具体类型 ,这样在使用实现代码的地方就不需要强制类型转换的步骤了。
   //方法名:方法名是对方法功能的描述,清晰的表述此方法完成的功能,方法名应该准确,清晰,简洁。
   //参数列表,参数数量不要过多,必要时引入参数对象,在方法中不要对参数进行二次赋值,一般情况下应该认为参数默认带有final修饰符,积极的进行方法参数合法性的检查,需要时使用保护性拷贝,要心里清楚我是在传过来的对象上直接修改还是需要一个对象的拷贝,然后在拷贝上修改。
   //方法名与参数列表被称为方法签名,用来在.java文件的同一个类中确定方法的唯一性,这里关于有方法重载问题的讨论。
   //除了以上这些基本的方法设计讨论,还有其它一些情况下没有讨论,比如对于构造方法及初始化方法不要调用可被子类重写的方法,以避免超类初始化失败的问题,等等。


哈哈,不错哦。

你看下我的目录安排,希望很多东西都会涉及到。
10 楼 H_eaven 2010-05-18  
借个地方总结下:
//方法设计的目标,好的设计与差的设计的表现,
//良好方法设计的做法,
//关键字:清晰性,重用性,封装。
//方法设计的讨论: 暂时分两个角度,一种是抽象含义上的,一种是物理结构上的,分这两种的出发点物理上的很容易理解,能够被一定的量化,而抽象含义上的比较范范,它需用根据环境做出具体的处理,
//一个方法中的代码抽象级别应该一致,这与一个类暴露给外部的接口方法的抽象程度相一致是一样的。
//比如现再有个方法要完成生成一个实例,生成实例需要分三个步骤完成,第一步实例化,第二步配置,第三步初始化,如果把这三个步骤的实现代码都写入到一个方法中,直观的是得到一个长长的方法,如果想要知道生成一个对象实例的步骤则必须把这个完整的代码看完,才能明白它表达的意思,
//而如果将这三步封装到方法中,则清晰的明白生成一个实例的步骤,一旦封装到方法中,则重用性也成为可能,因为变量表达式仅在方法内可见,而方法则在类内可见,如果通用性更强,则可以进一步放宽访问权限。
//针对这个角度的讨论好消息是我们知道目标了,坏消息是我们缺乏一些可以量化的标准。
   //一个方法的物理构件:方法头,方法体;
   //方法头包括:修饰符,返回值,方法名,参数列表(参数类型,数量,顺序),异常列表,
   //修饰符仅讨论访问修饰符,其它的不讨论,访问权限尽可能的底以保证封装性,私有方法对于类来说是类的实现代码,而不是类的API,这将使类的使用更简单,也将使以后修改类以保证二进制兼容性更容易,
   //返回值类型与参数类型尽可能使用抽象类型,这是面向对象设计基本原则依赖倒转(依赖抽象)所要求的,Java5支持协变返回类型,所以现再的使用方式应该是在定义抽象方法中使用抽象类型,而在实现方法中使用具体类型 ,这样在使用实现代码的地方就不需要强制类型转换的步骤了。
   //方法名:方法名是对方法功能的描述,清晰的表述此方法完成的功能,方法名应该准确,清晰,简洁。
   //参数列表,参数数量不要过多,必要时引入参数对象,在方法中不要对参数进行二次赋值,一般情况下应该认为参数默认带有final修饰符,积极的进行方法参数合法性的检查,需要时使用保护性拷贝,要心里清楚我是在传过来的对象上直接修改还是需要一个对象的拷贝,然后在拷贝上修改。
   //方法名与参数列表被称为方法签名,用来在.java文件的同一个类中确定方法的唯一性,这里关于有方法重载问题的讨论。
   //除了以上这些基本的方法设计讨论,还有其它一些情况下没有讨论,比如对于构造方法及初始化方法不要调用可被子类重写的方法,以避免超类初始化失败的问题,等等。
9 楼 mercyblitz 2010-05-18  
zhao103804 写道
看了LZ的归纳,发现自己写的命名完全不够规范。。
比如说 java.lang.Integer#parseInt(java.lang.String)
我就会命名为getInteger


返回值不同,如果要返回Integer类型的话,还是要使用java.lang.Integer#valueOf(java.lang.String);

看方法命名就知道parseInt会return int类型。
8 楼 mercyblitz 2010-05-18  
coffeesweet 写道
#这个符号,是新的java7里的吗???
具体干什么用的能说下吗?



这是Javadoc里面的方法识别符号,

比如可以这么些

{@link java.lang.Object#toString}这样就可以连接到java.lang.Object#toString()方法。
7 楼 lovesl 2010-05-18  
看了LZ的帖子,觉得在命名方法名上是该花点功夫。
6 楼 抛出异常的爱 2010-05-18  
zhao103804 写道
看了LZ的归纳,发现自己写的命名完全不够规范。。
比如说 java.lang.Integer#parseInt(java.lang.String)
我就会命名为getInteger

返回类型超出预期。

coffeesweet 写道
#这个符号,是新的java7里的吗???
具体干什么用的能说下吗?

类名#方法名
javadoc可以识别的语法
5 楼 coffeesweet 2010-05-18  
#这个符号,是新的java7里的吗???
具体干什么用的能说下吗?
4 楼 daqing15 2010-05-18  
很不错,深有感触
3 楼 zhao103804 2010-05-18  
看了LZ的归纳,发现自己写的命名完全不够规范。。
比如说 java.lang.Integer#parseInt(java.lang.String)
我就会命名为getInteger
2 楼 mercyblitz 2010-05-18  
whaosoft 写道
LZ归纳的这些命名模式够细的,平时还真没注意过
不过确实 有时在命名时 我会琢磨半天。。


好的方法命名不容易,尤其是言简意赅。
1 楼 whaosoft 2010-05-18  
LZ归纳的这些命名模式够细的,平时还真没注意过
不过确实 有时在命名时 我会琢磨半天。。

相关推荐

Global site tag (gtag.js) - Google Analytics