F's Blog

博客 收藏夹
Ruby 小记

08 May 2014

这里记录一下Ruby有用的小经验。

module

module的思想充分提现了Duck Typing的思想,想要什么方法,include进来就有了。

Module的“类”方法

Module如Class也是有类方法的,就是可以如M.a_method这样使用。

有两种方法实现,Module#module_function和extend另一个module。

经过module_function后,这个方法就会变成私有方法。

本来extend就是引入类方法的,而类只不过是特殊化的模块。

module M1
  def hi
    puts "hi"
  end

  module_function :hi
end

module M2
  extend M1
end

module M3
  module ClassMethods
    def hi
      puts "hi"
    end
  end

  extend ClassMethods
end

M1.hi  #=> hi
M2.send :hi  #=> hi
M3.hi  #=> hi

本来module是一个方法集,策略层面上的,这样就好被Mix-in进其它class或module。

require vs load

require就是把所require的代码复制到当前require的位置,并执行一遍,让下面的代码使用其定义的方法、类等代码。 使用require就是组织代码,让它们在不同的文件夹不同的文件里放着却可以逻辑上在一起,还有就是代码的复用。

load是每次遇见都会执行。

Dir.chdir

在ruby中执行外部的命令,如shell的ls,比如想要显示根目录下的文件列表,下面是不可以的:

`cd '\' && ls`

我想不这么轻松实现是有原因的:

而需要Dir.chdir:

Dir.chdir '\'
`ls`

private vs. protected

Ruby里的private方法能够被子类访问到,但访问时不能够加self。因为在Ruby中,private即为不能指定方法的接收者,只能是当前self且得省略。

能被子类访问有些像Java里的protected了。

Ruby中protected方法可以在子类里被调用,而不必指定必须为self调用的。

参考:

定义方法

由于是动态语言,父类可以调用子类里将会定义的方法而不需要在父类里提前声明一下。当然在父类的实例调用这个方法就会出错了。

class P
  def call_son_method
    son_method
  end
end

class S < P
  def son_method
    puts "son method"
  end
end

S.new.call_son_method  #=> son_method

由此可见ruby就是顺序执行的,碰到不认识的东西就按照其规则看看首先是不是变量,不是的话去找方法的定义。

a = a + 1

如上,在irb上中就执行上面的代码,那么结果是什么呢?

而我期望的是它会报NameError: undefined local variable or method `a’ for main:Object这样的错,然后停止对a赋值。

我没有看过ruby代码,猜想当代码执行到那一句时,先是解释”a=”,没有a这个变量,于是乎创建一个a并赋值nil,然后在”a+1”时a就是nil。

上面的想法可以通过执行

b = b.nil?

来验证,会返回true。

那么它带来的问题是当我在一个类中,想对一个方法的返回值进行处理然后赋值给同名的变量时,就会报错了。

有点儿绕口,看代码:

class C
  def a
    1
  end

  def add_wrong
    a = a + 1
  end

  def add_right
    a = self.a + 1
  end
end

c = C.new
c.a          #=> 1
c.add_wrong  #=> NoMethodError: undefined method `+' for nil:NilClass
c.add_right  #=> 2

类的实例变量 VS. 类变量

在类里面声明的@变量就是类实例变量,在实例方法里声明的@变量是实例变量,@@开头是类变量。

只有这样的变量才能穿过作用域之门,因为它们在一个作用域里。

class A
  @v = 1
  class << self
    def v
      @v
    end
  end
end

A.v #=> 1

上面的例子中@v就是A的类实例变量,因为它在A中定义。然后下面的class « self打开了A的类,所以可以在里面访问@v,而不是一个新的实例变量。

与类变量的区别是在于继承上,因为实例变量不会继承,而类变量会。

class_eval VS. instance_eval

顾名思义,class_eval是对类进行开包,而instance_eval是对当前实例进行开包。

但使用起来会有些迷惑:

class A
end

A.class_eval do
  def class_hi
    puts "class hi"
  end
end

A.instance_eval do
  def instance_hi
    puts "instance hi"
  end
end

a = A.new
a.class_hi #=> class hi
a.instance_hi #=> error:  undefined method `instance_hi' for #<A:0x00000008888888>

A.instance_hi #=> instance hi
A.class_hi #=> error:  undefined method `class_hi' for A:Class

对,虽然是class_eval,但要知道是打开当前类,相当于:

class A
  def class_hi
    puts "class hi"
  end
end

其实是定义的类的实例方法。而且class_eval只能作用于类或module,本来Class就是Module的子类。

而instance_eval是把当前类当作了一个实例,所以定义了一个类方法。

File.expand_path

在/tmp目录下创建一个rb_expand_path.rb的文件:

puts File.expand_path("path")

puts File.expand_path("path", "./")

puts File.expand_path("path", __FILE__)

在/tmp目录下执行:

$ pwd
/tmp

$ ruby rb_expand_path.rb
/tmp/path
/tmp/path
/tmp/rb_expand_path.rb/path

而如果我在home目录下执行:

$ pwd
/home/jack

$ ruby /tmp/rb_expand_path.rb
/home/jack/path
/home/jack/path
/tmp/rb_expand_path.rb/path

通过上述比较便可以总结:

同理require_relative也是这个作用,根据当前代码文件所在的目录,按照相对路径,寻找需要引用的文件。 而不是执行当前程序的目录。

#(1..3) VS. [1..3]

(1..3).class  #=> Range
[1..3].class  #=> Array
[1..3].first.class  #=> Range

这下就明白了,之前看[1..3]是Array啊,为什么each输出的不是1,2,3呢,原来它是有一个Range 1..3的Array而已。

ENV

ENV 是一个类方法,可以以哈希反回当前的环境变量,包括系统里的。

range

本文由 付豪 创作,采用署名 4.0 国际(CC BY 4.0)创作共享协议进行许可,详细声明