More: More Dengqinghua

Ruby对象模型

2017-11-28

本文在语言层面上总结了Ruby的对象模型.

阅读完该文档之后, 您将了解到:

该文档并非详尽Ruby的一切特性, 而是希望能抓住Ruby本身的特性和迷人之处.

1 Ruby基本结构

String
Array
Hash
Numeric
Symbol
Object

1.1 Symbol

:dsg, symbol用于很多地方, 其很重要一点为: symbol在内存中仅存储一份.

:dsg.object_id === :dsg.object_id #=> 返回true

而字符串不是.

"dsg".object_id == "dsg".object_id #=> 返回false

2 变量和常量

2.1 变量

2.1.1 普通变量(local variable)

ruby的变量非常简单

如果上下文没有给一个陌生的'变量'赋值, 那么该'变量'不是'变量', 而是方法

class User
  attr_accessor :name

  def set_name
    origin_name = name # name为方法, origin_name为变量
    self.name = "reseted #{name}" # name为方法, 重新赋值
  end
end

2.1.2 实例变量(instance variable)

存储在对象中的变量, 如下面的 @score

class User
  def initialize
    @score = 0
  end
end

2.1.3 类实例变量(class instance variable)

ruby中一切都是对象, 类也是对象, 存储在类中的实例变量为类实例变量

class People
  @alive = true
end

上述的 @alive 为People类的实例变量

类实例变量存储在该类中, 是不被子类共享的.

class Man < People
end

Man.instance_variable_get(:@alive) #=> 输出是nil, 而不是@alive

2.1.4 类变量(class variable)

类变量存在整个继承链中, 被所有继承的子类共享

class People
  @@alive = true
end

class Man < People
end

Man.class_variable_get(:@@alive) #=> true

Man.class_variable_set(:@@alive, false)

People.class_variable_get(:@@alive) #=> false

Rails中关于类变量的应用: mattr_accessor

2.1.5 全局变量(global variable)

全局共享

$DSG = "dsgv587"

2.2 常量

  • 常量在内存中只存在一份
  • 常量有查找算法
  • 如果查找不到该常量, 会走到Module#const_missing方法
  • Rails通过复写const_missing方法, 使得常量的加载可以自动require该常量对应的文件
  • 类名, module名都是常量

3 方法查找

3.1 方法定义

在ruby中, 方法只能定义一次, 不支持类似于java中overloading机制.

class User
  def get_name
  end

  def get_name(type)
  end
end

在User类中同时定义了两个同名方法 get_name, 在ruby中, 后定义的方法生效.

3.2 方法查找

3.2.1 ancestors

所有的方法查找都从当前的self对应的类的ancestor中去查找

如下面的例子所示:

class People
  def name
    puts "People name"
  end
end

module MixinUser
  def name
    puts "MixinUser"
  end
end

class User < People
  include MixinUser

  def name
    puts "User name"
    super
  end
end

User.new.name
#=>
User name
MixinUser

该部分Ruby metaprogramming讲解地非常好. 请查看该书的第二章

方法查找方式

  1. 获取所有的祖先链 ruby User.ancestors #=> [User, MixinUser, People, Object, Kernel, BasicObject]

  2. 从祖先链依次遍历, 看其中是否有name方法, 如果是super, 则从祖先链的上一级去找

3.3 影响祖先链的因素

方法存在类里面

  • prepend
  • sigleton method
  • self
  • include
  • super

最终如果找不到方法, 则去Object, Kernel, BasicObject找, 如果还找不到, 则进入method_missing方法

3.4 作用域和作用域门

参见Ruby metaprogramming的第四章: Blocks-Blocks Are closures-Scope

三个作用域门(Scope Gates)

  • Class definitions
  • Module definitions
  • Methods

class, moduledef关键字

在metaprogramming中的例子如下:

v1 = 1

class MyClass
  v2 = 2
  local_variables #=> [:v2]

  def my_method
    v3 = 3
    local_variables #=> [:v3]
  end

  local_variables #=> [:v2]
end

local_variables #=> [:v1]

可以其他方式打开作用域门, 即: define_method 代替 def, Class.new 代替 class, Module.new 代替 module

4 后记

个人理解中, Ruby是设计得非常好的, Ruby中遵循了最小惊讶原则: principle of least surprise, 理解了上面提到的Ruby的数据模型, 变量常量定义, 方法查找, 作用域之后, 基本上Ruby不会再给你其他的惊喜或者特殊的地方.

非常感谢Paolo Perrotta, 您的metaprogramming给我们打开了Ruby的另一扇门, 让我们能理解到Ruby的精髓和奇妙之处, 也支撑着我们以后学习其他语言如Go, Elixir, Javascript等, 都会思考相同的问题:

  • 语言的数据模型是什么?
  • 变量和方法是如何定义和寻找?
  • 变量的作用域是什么?
  • 这些语言提供的特性Ruby有吗?

对比着学习一门语言, 可以让我们事半功倍, 让精通第二门, 第三门语言的时间越来越短, 也能真正地感受到编程之美.

另外一本我们非常喜欢的书为Ruby Under a Microscope, 从C源码分析了Ruby的类, 方法是如何实现的, 尤其是AST, GC部分的分析, 非常地深入浅出. 很值得大家一看.

5 TODO

  • GC
  • Thread
  • Why ruby is slow compared with NodeJS or Java or C?