Ruby对象的初始化
Ruby对象的初始化

如果你了解其它OOP语言,你可能会觉得Ruby的构造器方法非常奇怪。

Directory

Getter和Setter

先看一段示例代码

class A
  def initialize
    @name = "noob"
  end
  # Getter
  def name
    @name
  end
  # Setter
  def name= (s)
    @name = s  
  end
end

a = A.new         # @name = "noob"
a.name = "alex"   # 调用Setter, @name = "alex"
p a.name          # 调用Getter, 返回 @name

对于像你我一样的老油条可能会觉得上面的例子稀疏平常。

但实际上它非常有趣。

我们在调用Setter方法name=的时候并没有像我们定义的那样使用,而是在name和=之间加了个空格,就好像我们是在对a.name进行赋值,而不是在调用一个方法一样。

这是Ruby给我们的一点漂亮的语法,但请注意这只能在以“=”结尾的方法上有用,不信你可以试一试。

initialize方法

像上面的例子那样一个一个去定义Getter和Setter明显不符合rubyist的简洁追求,所以我们有了类宏:

class B
  attr_accessor(:name)

  def initialize
    name = "noob"
  end
end

attr_accessor会为我们定义类似上一个例子里的Getter和Setter方法,其它类宏还有attr_writer和attr_reader。

这时候虽然我们已经定义了Setter方法,但是上面的代码并没有赋值”noob”到@name中,而是创建了一个新的局部变量name。

因为Setter方法比较特殊,在调用它时候需要显式用self作为接受者,即通过self.name=赋值。

其它大部分方法则不需要加上self,如果没有显式指定方法接受者,Ruby将自动使用self作为接受者。

在开头说了Ruby构造方法和其它OOP语言不一样,不一样在哪呢?

通过initialize方法进行初始化,会覆盖从父类继承的initialize方法,比如:

class Parent
  def initialize (name)
    @name = name
  end
end

class Child < Parent
  def initialize (grade)
    @grade = grade
  end
end

在上面的例子中,创建一个对象Child.new(99),这个对象不会有@name成员。

但这不意味着我们就需要在子类再实现一遍父类的功能,我们可以使用super关键字。

class Child < Parent
  def initialize (name, grade)
    super(name)
    @grade = grade
  end
end

乍一看好像Ruby把原来应该不需要手动干的时候给强加给我们了,但实际上这给了我们更多的自由,你可以自由地选择什么时候使用super。

懒惰加载

在初始化一个对象的时候,通常的做法就是一次性初始化所有成员变量。

要是有个变量初始化的时候太吃资源,而我们又不一定能用上它,你还会一次性就初始化所有成员变量吗?

class Foo
  def boo
    @boo ||= "bazz"
  end

  def boo= (x)
    boo = x
  end
end

创建一个对象foo = Foo.new,这时候foo不会有成员变量@boo,只有当boo方法被调用时才会对它进行初始化。

对于||=操作符,等价表达式是defined?(@boo) ? @boo : @boo = "bazz"

成员变量和局部变量有所不同,如果没有被初始化的局部变量直接使用,就会抛出undefined错误,但是成员变量完全没有这个问题。

所以当||=操作符的左侧部分是成员变量时,||=也等价于@boo ? @boo : @boo = "bazz"

文中部分代码例子引用自《Effective Ruby》