Ruby Eye for the Java Guy外文翻译资料

 2022-05-19 10:05

英语原文共 323 页,剩余内容已隐藏,支付完成后下载完整资料


Ruby Eye for the Java Guy

经过上一章的学习,你已经能够使用Ruby语言了,但感觉起来会像是有那种,在学习第二语言时只知道死记硬背的人的奇怪口音。所以在接下来的这一章里,你将通过学习一些更加贴和Ruby语言的惯用语法来改善你的“口音”。

3.1 扩展核心类

程序员们经常需要给一些语言运行库中已提供的类添加方法。而由于这些添加的方法需要对运行库中那些类本身的实例来说是可用的,通过子类继承的方式就往往不适用于此了。例如,Java和Ruby语言自身都没有提供方法来显示一个String对象是否是空白的(blank)(或者null、empty以及whitespace)。许多应用程序都希望对空白的输入内容做相同的处理,因此,一个检测输入是否空白的(blank-testing)方法就显得很有用了。无论是Java还是Ruby,在它们的开源社区里都提供有对空白内容的检查方法。比如下面就是一个Java语言下,Apache Commons Lang包提供的isBlank()方法的实现:

因为不能给一个核心类直接添加方法,Commons Lang包使用了Java的一贯方式,将那些要扩展加入的方法作为静态方法在另一个类里集中实现。上述的isBlank()方法就是在一个叫StringUtils的类内部实现的。所以,isBlank()方法的调用者在每次调用前面都要加上StringUtils这个辅助类的类名作为前缀:

但是,Ruby语言里的类是“开放”的,这意味着你可以随时修改它们。 所以,上面提到的问题在Ruby里的处理办法是----直接给String类添加blank?方法。Rails就是这么做的:

下面是一些对这个blank?方法的调用:

关于null

上文中Java版本的isBlank()方法要使用辅助类StringUtils来实现还有另外一个原因。即使可以把isBlank()方法直接放进String类中实现,在Java里,你也不会愿意这么做的。因为isBlank()方法需要在被值为null的String类对象调用时返回false,而在Java里,任何值为null的对象的方法调用都会引发NullPointerException异常。因而我们通过一个StringUtils类的静态方法来检查第一个参数,这样便回避了写出一个无意义地将this和null进行比较的String方法的陷阱。那么,为什么Ruby版本的实现办法不也这么干呢?

Ruby中的nil是一个实际的对象

在Ruby中,与Java 里的null对应的是nil。但是,nil却是一个实实在在的对象。你可以像调用其他对象的方法一样调用nil的方法。就我们目前正在讨论的问题来说,更重要的在于,你可以像处理任何其他Ruby对象一样,给nil这个对象直接添加方法。比如下面这段代码实现了让nil.blank?的调用返回true:

Rails还给其他的一些对象也提供了对应blank?方法的合理定义,比如true,false,空的数组或哈希表,数字类型甚至是Object类。

3.2 可变对象与不可变对象

大多数程序员可能会优先考虑使用可变对象(mutable objects)。但其实不可变对象(immutable objects)的用处也很大。

不可变对象有许多理想的属性:

(1)不可变对象是“线程安全”的。线程无法损害一个它不能改变的对象。

 (2)不可变对象使实现封装变得更容易。如果一个对象的部分属性保存在另一个不可变对象内,那么对应的访问方法就可以直接把这个不可变对象返回给调用者,而不用担心调用者会不会改变原对象的属性。

(3)不可变对象可以做出很好的散列键(hash key),因为它们的散列码不会改变。

Java语言通过final关键字来支持不可变。被final关键字标记的字段就是不可被改变的。要让整个对象不可变则需要在这个对象的所有字段前都标上final。

Ruby语言则使用了另一种非常不一样的实现途径。在Ruby里,是否可变被设定为某个对象实例的属性,而并非一整个类。任何一个实例都可以在调用freeze方法后转换为不可变对象:

无论在哪种语言环境下,一旦你准备将一个对象转换为不可变,你都得考虑到下面的这些问题:

·一个对象在被转换成不可变之前需要被完全初始化。在Java中,这意味着这个对象必须在每一个构造函数中的把它所有的字段都初始化。 而在Ruby里,具体的转换时间取决于你,因为freeze方法的调用是由你自行决定的。

·Setter方法对于不可变对象来说是非法的。在Java中,这个是在编译时保证的,所以不可变的类不会有setter方法。而在Ruby里,具体的实现方式也取决于你----但是如果一个不可变对象的writer方法被调用了,那就会抛出一个异常。

不可变对象也对使用者提出了一个重要要求:需要让值发生更改的方法(Modifier methods)不能直接更改不可变对象的值,因此就必须得把一个新对象作为返回值返回。如果使用了这样的方法,调用者就必须要记得把这个作为返回值的新对象捕获到。比如运行下面的代码是不会出现预期的结果的:

toUpperCase()方法不会改变s的值----Java里的字符串是不可变的(immutable),没法改变。和toUpperCase()方法类似的一些String方法其实都是把一个新的字符串对象作为返回值返回了,所以使用者一定要记得把返回的对象捕获到,比如下面的这个正确版本:

Ruby里的字符串并不是默认不可变的,但是和上面类似的问题还是会出现:

Ruby语言经常通过一个方法的名字来提供某种附加提示(hint),比如作为upcase方法的补充,就有upcase!方法。按照惯例,名字以感叹号结尾的方法是可变版本的(mutator),与之对应的,相同名字但不带感叹号的方法就不会改变对象的值而是返回一个新的对象。所以,上面的代码可以改成像下面这样:

3.3包和命名空间

基于人类语言,潜在的可以作为类名使用的词语数量是非常多的。尽管如此,命名冲突和指代模糊仍然有可能出现,特别是对于一些常用的词语。比如说我们创建了一个User类,而你也创建了一个同样名为User的类,这时候其他人要怎样来区分它们呢?

Java语言通过包来解决这个问题。包名约定使用小写字母并在单词与单词间以点分隔。包名一般会以反写的域名开头,这样就使得位于你代码组织中的其他各部分的命名便于理解。各个域名应该是世界独一无二的,这样一来名称冲突就不太可能发生了。包名会在类文件的最开头单独写出来:

下面这个看起来挺相似的类就和上面的完全不一样了,因为它属于另一个不同的包,com.relevancellc包:

当你使用前面两个类中的一个时,你必须指定它的完整名称,也就是包名加上类名。比如:

大多数时候你是不会碰到两个冲突的名字的。但如果真的碰到了,你可以选择导入(import)一个包。这样,你就可以在引用该包里的类时使用简写方式(只写类名),这种情况下,Java会依据import声明来判断你引用的是哪一个类:

Ruby程序则使用module(模块)来创建命名空间。下面这两个User类就分别处在两个module里:

为什么Ruby没有为模块指定一个命名方案

Java程序员被强烈建议在包名的开头反写域名。所以按照这个规定,Apache Commons Lang项目内的代码文件就会以org.apache.commons.lang开头。Ruby就没有类似的指导方案,所以模块(module)往往会根据其具体功能或商标等其他原因来命名。比如,Rails MVC controller代码就编写在Action-Controller模块内。Ruby程序员不需要那么担心命名冲突主要出于以下三个理由:

·在类或模块级别的名称冲突是比较容易解决的。Ruby的类安全依赖于“鸭子类型”(duck typing),而几乎与类或模块名称没有关系。

·“鸭子类型”依赖于方法名,所以你可能认为命名冲突会在这个层级出现。但其实,Ruby中可以很容易地重命名或取消定义方法,所以在实际操作中,方法名冲突很少导致问题。

·Ruby的名称冲突较少还因为Ruby程序使用更少的名字来开始。无论是在代码行数方面还是在使用名称的数量方面,动态类型语言往往都比静态类型语言更简洁。

另外值得注意的是,Java和Ruby对命名空间名称的要求其实都没有那么严格。一些流行的Java软件包都没有按照反写域名的规则来命名(比如 junit.framework)。我们(笔者)有时会使用Relevance作为Ruby程序中的顶层命名空间。 这当然不是我们的域名,但它是基于我们的组织名称的。我猜想这可能是受到了Java的影响hellip;hellip;

和Java类似,你也可以通过前缀来指定你具体引用的是哪一个模块。在Ruby里,这样的前缀后得跟上一个范围操作符“::”,然后在后面写上类名。

同样和Java类似,你可以不用老是写模块的前缀而只是简单地写上类名。在Ruby里,要实现这样的效果,通常是引入(include)一个模块:

尽管我们像是在类比Java里的import来使用include,但其实它们的真实性质是截然不同的。Java的import是一个编译时的概念,用于查找“真正的”包名。编译过后的Java字节码从不使用名称的简写形式(全是带上包名的完整形式)。而Ruby的include则是在运行时更改对象的模型,将一个模块插入到当前对象自身的继承层次结构中。你可以在使用include之前和之后分别调用ancestors方法来观察继承层次结构的变化:

上面的代码的运行结果会是下面这样:

因为include会改变对象模型,所以它的用处远远不止命名空间这么点。后面会提到include的更多可能用法。

3.4 代码部署

在Java中,你可以通过设置classpath(一个用于搜索编译类的本地目录列表)来直接管理代码部署。在一个更高的层面,你可以使用Java Network Launch Protocol在网络上部署组件和应用程序。Ruby则粗略地类比了Java,提供通过Ruby加载路径和RubyGems的部署方式。

加载路径(the load path)

在Java中,源代码文件被编译成类文件。 然后,这些类文件通常(但不总是)被汇总到JAR文件中。当一个Java程序运行时,一个名为类加载器的对象(class loader)会自动从合适的.jar或者.class文件中加载程序需要的类。这里就是通过搜寻classpath来找到这些文件的。考虑下面这个简单的程序:

当 ImplicitLoading类准备调用User的构造方法时,User类其实还没有被加载。Java的类加载器会在classpath中搜寻User。在这个简单的例子里,classpath就是一个包含了一列JAR文件和路径的环境变量。下面这条命令用来设置这个classpath:

给出这条命令后,Java的类加载器就会执行下面的步骤直到User类被找到:

1.在helpers.jar内查找路径名为com/relevancellce/User.class 的文件;

2.在classes目录下查找同一路径,就是classes/com/relevancellc/User.class 。

正如你所看到的,Java的类加载依赖于一些约定。首先,类通常位于同名的.class文件中。 其次,包名会被转换为目录和子目录;例如,一个名为com.relevancellc的包会转换成名为com/relevancellc的目录。

而在Ruby里,代码的加载跟Java基本上是完全不一样的。Ruby里,代替Java中classpath的是load path,他有一个Perl程序员可能会喜欢的简洁名字: $: 。

下面是一个来自irb会话的典型加载路径,格式调整为了适合页面的样子:

和Java不同,Ruby不是面向类的。一个源文件可能包含单个类,但也同样可能包含好几个类或者根本不包含类。因此,把类作为代码加载的单元是无意义的。相反地,源文件才是代码加载的单元。加载一个源文件要使用require关键字,不需要带上.rb后缀:

require rsquo;super_widgetrsquo;的使

全文共11865字,剩余内容已隐藏,支付完成后下载完整资料


资料编号:[12087],资料为PDF文档或Word文档,PDF文档可免费转换为Word

原文和译文剩余内容已隐藏,您需要先支付 30元 才能查看原文和译文全部内容!立即支付

以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。