在哪里储存那些有用的静态程序(static utility routies)
一旦你承认对两种基本的不同类型的类(通用的类和应用特定的类)的逻辑需求,你也只是离开解决另外一个棘手问题仅一步之遥:到哪里储存那些经常在手边使用的、但却是非面向对象的、有用的静态程序。
当我看到那些在应用特定的类中嵌入完全通用的程序片的时候,我总是感到很沮丧。假设现在有一个电子商务的应用类名叫Customer,包含了如下的方法:
private String surroundedBy(String string, String quote) {
return quote + string + quote;
}
Customer类的作者在这里包含了一个工具方法来生成字符串,这个方法被标记为surroundedBy(String, String)。该方法申明为私有,大概是因为作者认为这个方法应该是实现类Customer细节中的一部分。又因为这个方法没有申明为静态的,显然地,该方法是故意被申明为一个实例方法的。看上去很不错,真的吗?那么这个方法有什么问题呢?
首先,既然该方法被申明为实例方法,那么它为什么没有依赖于Customer对象的任何的状态呢(比如:对象的域)?它没有依赖于对象中的任何域是因为它根本就不需要或者使用任何的域;这个有用的方法只需要它本身的参数来完成它的功能而不需要任何其它的东西。这是它应该作为与类独立的有用方法的一个明证;换句话说,实际上这个方法根本就不是一个实例方法。
其次,在Customer类应该包含的内容当中这个方法没有起到合理的作用,所以它不应该简单的归属于Customer类。是不是看上去有些问题了?那么下面该怎么做呢?
正确的方式是,surroundedBy()方法应该属于一个专门用来处理字符数据类型的类,而不是类似于Customer性质的应用特定的类。而不幸的是,String类本身被申明为final类型,因此不能继承它创建子类(比如说BetterString类)来安置surroundedBy()方法。一个合理的变通方式是,定义一个新类专门来处理字符数据类型,我们取名为StringUtilities(或者短一些StringUtils,或者更短一些StringKit),然后把surroundedBy()方法改为public static类型的方法,就象这样:
public class StringKit {
// .. 许多其它的处理字符数据类型的程序片断
public static String surroundedBy(String string, String quote) {
return quote + string + quote;
}
// .. 许多其它的处理字符数据类型的程序片断
} // 类StringKit结束
那么我们在执行抽取方法的重构动作后获得了什么好处呢?
从短期来看,我们获得了两个好处:
# 写了非常好的可重用的(因此也是很有价值的)代码段,可以在将来不同的项目和应用中重复使用
# 通过消除不必要的方法提升了Customer类的抽象实现(abstraction implementation)
从长期来看,上面的重构技术会带来其它的效果,甚至很可能是更加重要和有价值的效果:
# 只需写较少的新代码(想来的代码只要调用StringKit.surroundedBy()就可以了)
# 随着更多的顶层逻辑和结构变得越来越清晰,您系统的整个架构也变得越来越简单
# 软件则由于更多的代码依赖于基础的构建模块库而变得更加的强大,这些模块将会被更全面的并且比“平铺”的应用代码更加频繁的进行测试。
不幸的是,实际编写出的代码往往夹杂了太多的类似surroundedBy()这种方法,很少有Java程序员会去重用那些方法,这是因为:
# 这些方法都是申明在应用特定的代码中,这些定义也都是不通用的因此也是不可重用的
# 这些方法对于其他希望使用它们的程序员来说甚至是不可见的,因为它们被定义为私有的或者是包范围(package-scope)的方法
一个方案是系统地辨认然后移动这些放错地方的可重用方法到相关特定领域的工具类中去。请参看最后面的“静态工具方法仓库,一个个人案例”,通过一个例子来看它是如何解决问题的。
动态包层次
因为代码重构能带来积极的正面效果,你还应该坚持不懈的准备好包层次的进化(evolving)。想像一下一棵逐渐长大并且成熟的树:随着Java类以及接口数量的增长,包结构中叶子和分支的比率也在不断的增长。无论什么时候当这个比率达到某个极限,出于本能你会试图释放分支上的压力,创建子分支并且将类和接口重新分配到新的分支中去。我总是将每个包中的类和接口数保持在较低的比率下,一般在7-10的范围内。(比较一下,java.lang中30个的数量或者更多以及java.util中40个的数量或者更多,是否更多取决于具体的API的版本。有没有对java.util中这么长的可复用类列表感到过窒息(overwhelmed)呢?为每个包保持比较低的类和接口数量可以防止程序员迷失在你的API中)
随着包分支数量的增长同样也需要对子包和父包保持一定的比率。如果这个比率达到了极限,那么你也应该本能地重新整理这些子包以减低父包的压力。将包结构保持一种美学上的平衡(比如,随着时间的推移,将结构始终保持成类似不规则碎片形的树结构,始终记住软件是一门艺术,也是科学)。
到现在我听到有很大的声音在喊:“动态包层次如何适应反向兼容类库(backward-compatible library classes)中的需要呢?”很明显,这里有一个有趣的冲突:类库是需要以用户友好的形式来增长的。幸运的是,用户对类库所依赖的主要的是类库所提供出来的不变的API(比如:容易记忆的类名,精确的方法命名)。[Java中引入(import)的关键字......] (Java's import language feature makes changing the source package from which a type hails less of an obstacle than it could be otherwise.)[我尝试翻译,但总觉着翻不确切,如果谁能翻译,请一定补充一下并告诉我,谢谢!shjunsuper@263.net]
在实际情况中,每次把类或接口从
一旦你承认对两种基本的不同类型的类(通用的类和应用特定的类)的逻辑需求,你也只是离开解决另外一个棘手问题仅一步之遥:到哪里储存那些经常在手边使用的、但却是非面向对象的、有用的静态程序。
当我看到那些在应用特定的类中嵌入完全通用的程序片的时候,我总是感到很沮丧。假设现在有一个电子商务的应用类名叫Customer,包含了如下的方法:
private String surroundedBy(String string, String quote) {
return quote + string + quote;
}
Customer类的作者在这里包含了一个工具方法来生成字符串,这个方法被标记为surroundedBy(String, String)。该方法申明为私有,大概是因为作者认为这个方法应该是实现类Customer细节中的一部分。又因为这个方法没有申明为静态的,显然地,该方法是故意被申明为一个实例方法的。看上去很不错,真的吗?那么这个方法有什么问题呢?
首先,既然该方法被申明为实例方法,那么它为什么没有依赖于Customer对象的任何的状态呢(比如:对象的域)?它没有依赖于对象中的任何域是因为它根本就不需要或者使用任何的域;这个有用的方法只需要它本身的参数来完成它的功能而不需要任何其它的东西。这是它应该作为与类独立的有用方法的一个明证;换句话说,实际上这个方法根本就不是一个实例方法。
其次,在Customer类应该包含的内容当中这个方法没有起到合理的作用,所以它不应该简单的归属于Customer类。是不是看上去有些问题了?那么下面该怎么做呢?
正确的方式是,surroundedBy()方法应该属于一个专门用来处理字符数据类型的类,而不是类似于Customer性质的应用特定的类。而不幸的是,String类本身被申明为final类型,因此不能继承它创建子类(比如说BetterString类)来安置surroundedBy()方法。一个合理的变通方式是,定义一个新类专门来处理字符数据类型,我们取名为StringUtilities(或者短一些StringUtils,或者更短一些StringKit),然后把surroundedBy()方法改为public static类型的方法,就象这样:
public class StringKit {
// .. 许多其它的处理字符数据类型的程序片断
public static String surroundedBy(String string, String quote) {
return quote + string + quote;
}
// .. 许多其它的处理字符数据类型的程序片断
} // 类StringKit结束
那么我们在执行抽取方法的重构动作后获得了什么好处呢?
从短期来看,我们获得了两个好处:
# 写了非常好的可重用的(因此也是很有价值的)代码段,可以在将来不同的项目和应用中重复使用
# 通过消除不必要的方法提升了Customer类的抽象实现(abstraction implementation)
从长期来看,上面的重构技术会带来其它的效果,甚至很可能是更加重要和有价值的效果:
# 只需写较少的新代码(想来的代码只要调用StringKit.surroundedBy()就可以了)
# 随着更多的顶层逻辑和结构变得越来越清晰,您系统的整个架构也变得越来越简单
# 软件则由于更多的代码依赖于基础的构建模块库而变得更加的强大,这些模块将会被更全面的并且比“平铺”的应用代码更加频繁的进行测试。
不幸的是,实际编写出的代码往往夹杂了太多的类似surroundedBy()这种方法,很少有Java程序员会去重用那些方法,这是因为:
# 这些方法都是申明在应用特定的代码中,这些定义也都是不通用的因此也是不可重用的
# 这些方法对于其他希望使用它们的程序员来说甚至是不可见的,因为它们被定义为私有的或者是包范围(package-scope)的方法
一个方案是系统地辨认然后移动这些放错地方的可重用方法到相关特定领域的工具类中去。请参看最后面的“静态工具方法仓库,一个个人案例”,通过一个例子来看它是如何解决问题的。
动态包层次
因为代码重构能带来积极的正面效果,你还应该坚持不懈的准备好包层次的进化(evolving)。想像一下一棵逐渐长大并且成熟的树:随着Java类以及接口数量的增长,包结构中叶子和分支的比率也在不断的增长。无论什么时候当这个比率达到某个极限,出于本能你会试图释放分支上的压力,创建子分支并且将类和接口重新分配到新的分支中去。我总是将每个包中的类和接口数保持在较低的比率下,一般在7-10的范围内。(比较一下,java.lang中30个的数量或者更多以及java.util中40个的数量或者更多,是否更多取决于具体的API的版本。有没有对java.util中这么长的可复用类列表感到过窒息(overwhelmed)呢?为每个包保持比较低的类和接口数量可以防止程序员迷失在你的API中)
随着包分支数量的增长同样也需要对子包和父包保持一定的比率。如果这个比率达到了极限,那么你也应该本能地重新整理这些子包以减低父包的压力。将包结构保持一种美学上的平衡(比如,随着时间的推移,将结构始终保持成类似不规则碎片形的树结构,始终记住软件是一门艺术,也是科学)。
到现在我听到有很大的声音在喊:“动态包层次如何适应反向兼容类库(backward-compatible library classes)中的需要呢?”很明显,这里有一个有趣的冲突:类库是需要以用户友好的形式来增长的。幸运的是,用户对类库所依赖的主要的是类库所提供出来的不变的API(比如:容易记忆的类名,精确的方法命名)。[Java中引入(import)的关键字......] (Java's import language feature makes changing the source package from which a type hails less of an obstacle than it could be otherwise.)[我尝试翻译,但总觉着翻不确切,如果谁能翻译,请一定补充一下并告诉我,谢谢!shjunsuper@263.net]
在实际情况中,每次把类或接口从
| 对此文章发表了评论 |
