脱离Rails看Ruby:在搭上Rails潮流之前了解Ruby -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

简介:Ruby on Rails 只是使 Ruby 非常强大的方面之一,就像 EJB 只是 Java企业平台的一部分一 样,

脱离Rails看Ruby:在搭上Rails潮流之前了解Ruby

。Andrew Glover 揭示了 Java 开发人员可以使用 Ruby 做什么。

在开始这篇文章之前,我需 要澄清一些事情。首先,这不是一篇关于 Ruby on Rails 的文章。如果您希望了解 Rails,每周(甚至 每小时)都有相关的文章和 blog 出现,它们都对这个令人兴奋的框架的众多特性大加推崇;请参见 参 考资料 中的列表。其次,本文并不是想预言,Java 面对像 Ruby on Rails 这样更好的 语言、工具和框 架会衰败下去。所以本文并不涉及近来通常与 Ruby 最相关的主题。

别误会 —— 我认为 Rails 是令人难以置信的!它的功能极其强大,已经明显地改变了 Web 开发的 面貌和步调。我只想指出一点:Ruby 要比 Rails 功能丰富,尤其是从 Java 开发人员的视角来看。

Rails 的专长是 Web 站点开发;但是,我自己没有构建过全新的 Web 站点。我所工作的大多数 Web 站点已经 使用 Struts、Tapestry 或其他技术构建起来了。在我利用 Ruby 时,基本上是将它作为一种 与 Java 平台衔接的开发实践。所以在本文中,我将谈谈如果您主要是 Java 开发人员,那么应该如何利 用 Ruby 进行开发。

感觉是如此不同

Ruby 的语法与 Java 语言的语法不同。首先,Ruby 没有方括号或分号,而且它使类型成为完全可选 的。有人可能会说 Ruby 的语法很简洁,这正是它的意图:这种语言使开发人员可以迅速编写简洁的代码 。

如果用 Java 语言和 Ruby 分别定义同一个类,通过比较,就能够看到这种简洁性。我先给出用 Java 语言编写的两个类 —— Word 和 Definition(就像是词典)。在图 1 所示的简单类图中,可以看出这 两个类有几个关系(如果这种复杂性看起来不自然,请忍耐一下;这是有意义的!):

一个 Word 可以拥有一个同义词(Word 的实例)集合。

一个 Word 还拥有一个 Definition 集合。

一个 Definition 拥有一个对 Word 的聚合关联。

图 1. 具有单词和定义的简单词典

用 Java 语言编写的类定义

在清单 1 中,用 Java 语言定义了 Word 类。注意,必须对 Definition 和同义词集合进行关系检验 。这是因为在这个例子中,最初创建 Definition 时可以不带 Word 关系,最初定义 Word 时也可以不带 Definition。

清单 1. 用 Java 语言编写的 Word 类

package com.vanward.dictionary;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Word {  
 private String spelling;
 private String partOfSpeech;
 private Collection definitions;
 private Collection synonyms;
 public Word(String spelling, String partOfSpeech) {
  this.spelling = spelling;
  this.partOfSpeech = partOfSpeech;
  this.definitions = new ArrayList();
  this.synonyms = new ArrayList();
 }
 public Word(String spelling, String partOfSpeech,
  Collection definitions) {
   this(spelling, partOfSpeech);
   if(definitions != null){
    for(Iterator iter = definitions.iterator(); iter.hasNext();){
     this.validateRelationship((Definition)iter.next());
    }
    this.definitions = definitions;
   }    
 }
 public Word(String spelling, String partOfSpeech,
  Collection definitions, Collection synonyms) {
   this(spelling, partOfSpeech, definitions);
   if(synonyms != null){
    this.synonyms = synonyms;
   }
 }
 private void validateRelationship(Definition def){
  if(def.getWord() == null || def.getWord() != this){
   def.setWord(this);
  }
 }
 public Collection getDefinitions() {
  return definitions;
 }  
 public void addDefinition(Definition definition) {
  this.validateRelationship(definition);    
  this.definitions.add(definition);
 }
 public String getPartOfSpeech() {
  return partOfSpeech;
 }
 public void setPartOfSpeech(String partOfSpeech) {
  this.partOfSpeech = partOfSpeech;
 }
 public String getSpelling() {
  return spelling;
 }
 public void setSpelling(String spelling) {
  this.spelling = spelling;
 }
 public Collection getSynonyms() {
  return synonyms;
 }
 public void addSynonym(Word synonym) {
  this.synonyms.add(synonym);
 }  
}

清单 1 中的 Word 类相当简单;它是一个 JavaBean,具有一个构造函数链,使用户能够用各种属性 集创建 Word。还要注意,它的 synonyms 和 definitions 属性是 只读的(即没有针对它们的设置方法 )。只能添加 一个 Definition 实例,或者为一个同义词添加 另一个 Word。

在清单 2 中是相关的 Definition 类,这个类与 Word 类的相似之处是,它的 exampleSentences 属 性没有对应的 set() 方法:

清单 2. 用 Java 语言编写的 Definition 类

package com.vanward.dictionary;
import java.util.Collection;
public class Definition {
 private Word word;
 private String definition;
 private Collection exampleSentences;
 public Definition(String definition){
  this.definition = definition;
  this.exampleSentences = new ArrayList();
 }
 
 public Definition(String definition, Word word) {      
  this(definition);
  this.word = word;
 }
 public Definition(String definition, Word word,
  Collection exampleSentences) {
   this(definition, word);
   if(exampleSentences != null){
    this.exampleSentences = exampleSentences;
   }

 }
 public String getDefinition() {
  return definition;
 }
 public void setDefinition(String definition) {
  this.definition = definition;
 }
 public Collection getExampleSentences() {
  return exampleSentences;
 }
 public void addExampleSentence(String exampleSentence) {
  this.exampleSentences.add(exampleSentence);
 }
 public Word getWord() {
  return word;
 }
 
 public void setWord(Word word) {
  this.word = word;
 }
}

用 Ruby 编写的类定义

在清单 3 中,可以看到用 Ruby 定义的这两个类。清单 3 确实 看起来很不一样,不是吗?

清单 3. 用 Ruby 编写的相同类

module Dictionary
 class Word
  attr_reader :spelling, :part_of_speech, :definitions, :synonyms
  attr_writer :spelling, :part_of_speech
  def initialize(spelling, part_of_speech, definitions = [], synonyms = [])
   @spelling = spelling
   @part_of_speech = part_of_speech   
   definitions.each{ |idef| idef.word = self}   
   @definitions = definitions   
   @synonyms = synonyms
  end
  def add_definition(definition)
   definition.word = self if definition.word != self
   @definitions << definition  
  end
  def add_synonym(synonym)
   @synonyms << synonym
  end
 end
 class Definition
  attr_reader :definition, :word, :example_sentences
  attr_writer :definition, :word
  def initialize(definition, word = nil, example_sentences = [])   
   @definition = definition
   @word = word
   @example_sentences = example_sentences
  end
 end
end

从清单 3 中可以看出,Ruby 的语法非常简洁。但是,不要让这种简洁性欺骗了您,这段代码中有许 多内容!首先,在一个模块 中定义了两个类,模块本质上相当于 Java 语言中的 包。另外,能够在一个 文件中定义这些类,而 Java 语言要求用两个文件。您还会注意到,Ruby 的构造函数名为 initialize, 而 Java 语言中的构造函数使用类名进行命名。

创建对象实例

在 Ruby 中创建新的对象实例的方式是不一样的。Ruby 并不使用 Java 代码中的 new ObjectInstance() 语法,Ruby 实际上支持在对象上调用 new 方法,即在内部调用 initialize 方法。 在清单 4 中,可以看到如何在 Ruby 中创建一个 Word 实例和一些对应的 Definition:

清单 4. 在 Ruby 中创建新的对象实例

require "dictionary"
happy_wrd = Dictionary::Word.new("ebullient", "adjective")
    
defin_one = Dictionary::Definition.new("Overflowing with enthusiasm")
defin_two = Dictionary::Definition.new("Boiling up or over")
happy_wrd.add_definition(defin_one)
happy_wrd.add_definition(defin_two)

在清单 4 中,我用 Ruby 的 require 方法(这个方法可以在 Kernel 类中找到)“导入了” dictionary 模块。然后通过 Object.new 语法创建一个新的 Word 实例(ebullient)。尽管导入了 dictionary 模块,但是仍然需要对对象实例进行限定,因此采用 Dictionary::Word。如果在 require 子句后面编写了 include Dictionary,那么也可以去掉 Dictionary:: 前缀。

默认参数值

您是否注意到了,在清单 4 中创建 happy_wrd 实例时没有 指定 Definition 或同义词集合。我只为 spelling 和 part_of_speech 传递了值。可以进行这样的省略是因为 Ruby 支持参数的默认值。在清单 3 定义的 Word 的 initialize 方法中,指定了 definitions = [] 和 synonyms = [] 作为参数,这基 本上就是告诉 Ruby 如果调用者没有提供这些集合,就将它们默认设置为空集合。

还要注意,在 清单 3 中 Definition 的 initialize 方法如何通过将 example_sentences 设置为一 个空集合,从而支持默认参数(word 的默认值 nil 是 Java 语言中 null 的 Ruby 版本)。回到 清单 1,在这里必须创建三个构造函数 才能获得 Java 语言提供的相同灵活性!

现在,我用灵活的 initialize() 方法创建另一个 Word 实例,见清单 5:

清单 5. 灵活性的表现!

require "dictionary"
defin = Dictionary::Definition.new("Skill in or performance of tricks")
defin_two = Dictionary::Definition.new("sleight of hand")
    
defs = [defin, defin_two]
    
tricky_wrd = Dictionary::Word.new("prestidigitation", "noun", defs)

在定义两个 Definition 之后,将它们添加进一个集合中(集合看起来就像 Java 语言中的数组)。 然后将这个集合传递给 Word 的 initialize() 方法。

集合的处理

Ruby 的集合处理同样简单得令人吃惊;看看 Word 类中的 add_definition 和 add_synonym 方法就 知道了。<< 语法被重载,表示 add。如果再看看 清单 2 中的 Definition 类,就会发现 Java 语言中的对应代码复杂多了:this.exampleSentences.add(exampleSentence)。

Ruby 的集合处理非常简洁。在清单 6 中,可以看到组合集合(使用 + 操作符)和访问成员(通过 [ position ])是多么容易,没有 什么值得操心的东西!

清单 6. 简练的集合

require "dictionary"
idef_1 = Dictionary::Definition.new("Sad and lonely because deserted")
idef_2 = Dictionary::Definition.new("Bereft; forsaken")
defs = [idef_1, idef_2]
idef_3 = Dictionary::Definition.new("Wretched in appearance or condition")
idef_4 = Dictionary::Definition.new("Almost hopeless; desperate")
defs_2 = [idef_3, idef_4]
n_def = defs + defs_2 #n_def is now [idef_1, idef_2, idef_3, idef_4]
n_def[1]  # produces idef_2
n_def[9]  # produces nil
n_def[1..2] # produces [idef_2, idef_3]

清单 6 中的代码只触及到了 Ruby 集合处理的皮毛!

RubyBean?

在 清单 3 中的两个类中您可能会注意到,Ruby 支持一种定义属性的简写方式:attr_reader 和 attr_writer,

电脑资料

脱离Rails看Ruby:在搭上Rails潮流之前了解Ruby》(https://www.unjs.com)。因为使用了这种方式,所以可以在 Word 类中设置 和获取 对应的属性,如清单 7 所示:

清单 7. attr_reader 和 attr_writer 的作用

require "dictionary"
wrd = Dictionary::Word.new("turpitude", "Noun")
wrd.part_of_speech # "Noun"
wrd.spelling    # "turpitude"
wrd.spelling = "bibulous"
wrd.spelling    # "bibulous"
syns = [Dictionary::Word.new("absorptive", "Adjective"),
 Dictionary::Word.new("imbibing", "Noun") ]
# Danger!
wrd.synonyms = syns = syns #Exception: undefined method `synonyms='...

attr_reader 和 attr_writer 都不是关键词,而是 Ruby 中的实际方法(在 Module 类中),它们以 符号作为参数。符号 是前面有冒号(:)的任何变量,更妙的是符号本身也是对象!

注意,因为在 清单 3 中使 synonyms 成为只读的,所以 Ruby 拒绝执行清单 7 中的最后一行代码。 另外,还可以使用 attr_accessor 方法编写属性声明代码,指出属性是既可读又 可写的。

观察 Ruby 的迭代

灵活的迭代方式也是用 Ruby 编写代码时的乐趣之一。看一下清单 8,这里给出了 Word 的 initialize() 方法:

清单 8. 闭包是很方便的

def initialize(spelling, part_of_speech, definitions = [], synonyms = [])
 @spelling = spelling
 @part_of_speech = part_of_speech   
 definitions.each{ |idef| idef.word = self}   
 @definitions = definitions   
 @synonyms = synonyms
end

清单 8 的第四行有点儿与众不同。为了让初学者看明白,在 definitions 实例上调用 each 方法时 使用了花括号。each 方法本质上就像 Java 语言中的 Iterator,但是它更简洁。在清单 8 中,each 方 法处理迭代的细节,使调用者能够将注意力集中在想要的效果上。在这个例子中,传递一个代码块来表示 以下意思:对于集合中的每个值 —— 即 idef(这是 Definition 的一个实例),将它的 word 属性设 置为 self(这相当于 Java 语言中的 this)。

清单 9 给出 Java 语言中等效的代码行(取自 清单 1 中的 Word 构造函数):

清单 9. Ruby 的 each 方法就像 Java 的 Iterator

for(Iterator iter = definitions.iterator(); iter.hasNext();){
 this.validateRelationship((Definition)iter.next());
}

是的,是的,是的...!

Java 5 的泛型和新的 for 循环语法比清单 9 中的代码好得多。Ruby 也 支持大家熟悉的 Java 循环 结构,比如 for 和 while;但是在实践中很少用到这些结构,因为 Ruby 中的几乎所有东西都支持迭代 表示法。例如,在清单 10 中,可以看出迭代文件的内容是多么容易:

清单 10. 迭代非常简单

count = 0
File.open("./src/dictionary.rb").each { |loc| puts "#{count += 1}:" + loc }

Ruby 中支持 each 方法的任何类(比如 File)都允许以这种方式进行迭代。顺便说一句,Ruby 的 puts 方法(见清单 10)相当于 Java 语言的 System.out.println。

条件语法

讨论了循环之后,我们来看看 清单 3 的 Word 类中的条件语句。在清单 11 中,单独给出了 add_definition() 方法:

清单 11. 漂亮的条件语法

def add_definition(definition)
 definition.word = self if definition.word != self
 @definitions << definition  
end

仔细看看第二行代码。看得出 if 语句的逻辑吗?可以 将它改写为清单 12 所示的一般形式,但是清 单 11 不是更好吗?

清单 12. 表达条件有多种方式

def add_definition(definition)
 if definition.word != self
  definition.word = self
 end
 @definitions << definition  
end

在 Java 语言中,如果条件结构的体只有一行,那么可以省略括号。在 Ruby 中,如果条件结构的体 只有一行,那么可以编写 清单 11 中所示的表达式。还要注意,同样的条件还可以写成 definition.word = self unless definition.word == self,这使用了 Ruby 的 unless 特性。这很棒 ,不是吗?

Ruby 中的多态性

因为 Ruby 是动态类型语言,所以它不需要接口。但是要记住,接口的功能在 Ruby 中是存在的,只 是以灵活得多的方式表现出来。Ruby 中的多态性被亲切地称为 “duck typing”(意思是,如果它走起 路来像鸭子,叫起来也像鸭子,那么它一定是鸭子!),这种多态性只是对方法名进行匹配的问题。我们 来比较一下 Ruby 和 Java 语言中的多态性。

Java 的多态性

在 Java 语言中利用多态性的方式之一是声明一个接口类型,并让其他类型实现这个接口。然后就可 以按照接口类型引用实现此接口的对象,并调用这个接口中存在的任何方法。例如,在清单 13 中,定义 了一个简单的接口 Filter:

清单 13. 简单的 Java 接口

package com.vanward.filter;
public interface Filter { 
 boolean applyFilter(String value);
}

在清单 14 中,定义了一个名为 RegexPackageFilter 的实现类,它应用一个正则表达式来进行过滤 :

清单 14. RegexPackageFilter 实现了 Filter

package com.vanward.filter.impl;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternCompiler;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import com.vanward.filter.Filter;
public class RegexPackageFilter implements Filter {
 private String filterExpression;
 private PatternCompiler compiler;
 private PatternMatcher matcher;
 public RegexPackageFilter() { 
  this.compiler = new Perl5Compiler();
  this.matcher = new Perl5Matcher();
 }
 public RegexPackageFilter(final String filterExpression){
  this();
  this.filterExpression = filterExpression;
 }
 public boolean applyFilter(final String value) {
  try{    
   Pattern pattrn = this.getPattern();
   return this.matcher.contains(value, pattrn);
   }catch(MalformedPatternException e){
    throw new RuntimeException("Regular Expression was uncompilable " +
     e.getMessage());
   }
 } 
 
 private Pattern getPattern() throws MalformedPatternException{
  return compiler.compile(this.filterExpression);    
 }
}

现在,假设有 Filter 接口的多个实现(比如 RegexPackageFilter、ClassInclusionFilter 类型和 SimplePackageFilter 类型)。为了使应用程序的灵活性最大化,其他对象现在可以引用接口类型 (Filter),而不是实现者,如清单 15 所示:

清单 15. 多态性非常酷

private boolean applyFilters(final String value, final 

Filter[] filters){
  boolean passed = false;
  for(int x = 0; (x < filters.length && !passed); x++){
    passed = filters[x].applyFilter(value);
  }
  return passed;
}

Ruby 多态性

在 Ruby 中没有接口!只要方法名匹配,就可以利用多态性。看看吧。

在清单 16 中,用 Ruby 重新创建了 Java Filter 类型。注意,每个类之间并没有关系(只不过它们 都拥有同一个方法 apply_filter)。是的,这两个类应该被重构以扩展 Filter 基类;但是,在这里我 只是想展示在 Ruby 中如何利用多态性,而类并不共享相同的类型。

清单 16. 过滤我,Ruby!

class RegexFilter
  attr_reader :fltr_exprs
  def initialize(fltr_exprs)
    @fltr_exprs = fltr_exprs
  end
  def apply_filter(value)
    value =~ @fltr_exprs
  end
end
class SimpleFilter
  attr_reader :fltr_exprs
  def initialize(fltr_exprs)
    @fltr_exprs = fltr_exprs
  end
  def apply_filter(value)
    value.include?(@fltr_exprs)
  end
end

注意在清单 16 中,可以通过 =~ 语法在 RegexFilter 的 apply_filter() 方法中创建一个正则表达 式匹配器。(如果您是 Groovy 用户,就应该熟悉它;清单 16 说明 Groovy 受到了 Ruby 的强烈影响! )

duck typing 的表现

在清单 17 中,我使用 Ruby 的 Test::Unit(这就像 Java 的 JUnit)来演示 duck typing。顺便说 一句,在 Ruby 中建立自动测试只需扩展 Test::Unit 并添加以 test 开头的方法。这与 JUnit 很相似 ,对吗?

清单 17. 多态性过滤

require "test/unit"
require "filters"
class FiltersTest < Test::Unit::TestCase
  def test_filters
    fltrs = [SimpleFilter.new("oo"), RegexFilter.new(/Go+gle/)]
    fltrs.each{ | fltr |
      assert(fltr.apply_filter("I love to Goooogle"))
    }
  end
end

注意,在 test_filters() 方法中,创建了一个包含两个类 SimpleFilter 和 RegexFilter 的集合。 这些类并不共享同一个基类,但是在对集合进行迭代时,仍然可以简单地调用 apply_filter() 方法。

还要注意 Ruby 多么轻松地支持正则表达式。要创建正则表达式,只需使用 / regex / 语法。因此, 清单 17 中 RegexFilter 的正则表达式是一个大写的 G,后面是一个或多个 o,最后是 gle。

mix-in

Ruby 没有接口,但是它有 mix-in。可以把 mix-in 看成多重继承,但是它没有 多重继承的麻烦。 mix-ins 是模块(不能被实例化),其中包含类可以选择包含的方法。这些模块方法会变成包含它们的类 的实例方法。

例如,在 JUnit 中,Assertion 类是一个具体的类,其中包含许多 static 断言方法,我们熟悉的 TestCase 类会扩展这些方法。因此,任何 TestCase 实现类都可以在它自己的已定义方法中引用断言方 法。

Ruby 的单元测试框架有点儿不一样。它并未定义 Assertion 类,而是定义了一个 Assertions 模块 。这个模块定义了许多断言方法,但是 Ruby 不对这个模块进行扩展,Ruby 的 TestCase 类以 mix-in 的形式包含 assertion。因此,这些断言方法现在都是 TestCase 上的实例方法,如 清单 17 所示。

结束语

您已经看到,Ruby 的语法与 Java 语言很不一样,但是非常容易掌握。另外,某些事情用 Ruby 做起 来比用 Java 语言容易得多。

学习多种自然语言的经验告诉我们,可以混合使用不同的编程语言是件好事儿。能够用多种语言进行 编程,使您在面对各种编程任务时具有更大的灵活性。这还会提升您的编程母语的价值。

正如在本文开头所说的,我主要是 Java 开发人员,但是我发现有许多办法可以将 Ruby(和 Groovy 、Jython 等等)用在工作中。而且这么做的时候不需要使用 Rails!如果您放弃了 Ruby on Rails,因 为您实际上不需要 在短时间内构建购物车应用程序,那么好好了解一下 Ruby 本身吧。我认为您会喜欢 自己看到的东西。

最新文章