一个简单的测试

1
2
3
4
5
6
7
8
9
10
11
12
// gem install test-unit --verbose
def to_word(s)
s.gsub(/\d/, '')
end

require 'test/unit'

class ToWordTest < Test::Unit::TestCase
def test1
assert_equal 'hello world', to_word('234234he345345l345345lo345345 wo345r4353ld')
end
end

在 class String 上添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class String
def to_word
self.gsub(/\d/, '') # self 可以省略
end
end

require 'test/unit'

class ToWordTest < Test::Unit::TestCase
def test1
assert_equal 'hello world', '234234he345345l345345lo345345 wo345r4353ld'.to_word
end
end

Class

重复的 class

1
2
3
4
5
3.times do
class C
p 'hello'
end
end

验证只是 class 可以重复声明且会自动合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class C
def x
p 'x'
end
end

class C
def y
p 'y'
end
end

z = C.new
z.x
z.y

自定义一个 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyClass
def initialize
@v = 1
end
def add
@v += 1
end
end

obj = MyClass.new

p obj.class
p obj.add
p obj.instance_variables
p MyClass.instance_variables
p obj.methods
p MyClass.instance_methods
p obj.methods.grep(/add/)

证明,一个对象的实例变量储存在自身,一个对象的实例方法存储在其类身上

superclass (类似于 JS 的原型链)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
p '----String----'
p String.superclass # Object
p Object.superclass # BasicObject
p BasicObject.superclass # nil

p String.is_a? Class

p '----Array----'
p Array.superclass # Object
p Array.is_a? Class

class MyClass; end
p MyClass.superclass # Object
p MyClass.is_a? Class

p 'class is a Class?'
p Class.is_a? Class

p Class.superclass # Module
# 模块是低配版的类,类是高配版的模块

模块是低配版的类,类是高配版的模块

常量

1
2
3
4
5
6
7
8
9
10
11
12
Const1 = 'root const'
module MyModule
Const1 = 'outer const'
class MyClass
Const1 = 'inner const'
p ::Const1 # 'root const'
end
end

p Const1 # 'root const'
p MyModule::Const1 # 'outer const'
p MyModule::MyClass::Const1 # 'inner const'

使用 :: 打印出内部的常量,直接使用 :: 可以打印出根的常量

1
2
p '------------'
p MyModule.constants

使用 MyModule.constants 得到 MyModule 的内部的所有一级常量

1
2
3
4
5
6
7
8
9
Const1 = 'root const'
module MyModule
Const1 = 'outer const'
class MyClass
Const1 = 'inner const'
p 'Module.nesting'
p Module.nesting # [MyModule::MyClass, MyModule]
end
end

使用 Module.nesting 得到 当前作用域的层级结构

祖先链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass
def my_class
'my_class'
end
end

class MySubClass < MyClass # < 表示继承

end

obj = MySubClass.new

p obj.my_class
p MySubClass.ancestors # [MySubClass, MyClass, Object, Kernel, BasicObject] # 其中 Kernel 是祖先模块

module

模块和类非常非常像,只是模块不能继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module M1
def my_methods
'M1#my_methods'
end
end

class C1
include M1 # 相当于 C1 < M1,但是不能继承一个模块,只能继承一个类
end

class A1 < C1

end

p A1.ancestors # [A1, C1, M1, Object, Kernel, BasicObject] # 从 C1 ~ Object 之间有个 M1 得出,Object 里肯定有 include Kernel,所以上面的祖先链中会多出现一个 Kernel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module M1
def my_methods
'M1#my_methods'
end
end

class C1
prepend M1
end

class A1 < C1

end

p A1.ancestors # [A1, M1, C1, Object, Kernel, BasicObject]

使用 include (上面增加)和 prepend(下面增加)
可以在类中引入模块,用来改变方法查找顺序,使得继承组合更加多样化

self (method 的查找方法)

self 是当前对象

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
def test_self
@var = 10
me_method
self
end
def me_method
@var += 1
end
end

obj = MyClass.new
p obj.test_self # #<MyClass:0x00000000051488b8 @var=11>

可以看到 @var 已经是 11 了,
注意:这里执行到 me_method 的时候,不是像js一样直接往下在当前对象上找到 me_method 这个方法,而是这样的执行顺序:
先找 obj 上的 test_self, 在MyClass 上找到,执行该方法,该方法声明了 @var 并执行 me_method 方法(注意:这里的 me_method 会再次找 obj => MyClass => def me_method,再执行)注意调用该方法时,self已经变了,所以 me_method 才能给 @var 加一,最后返回 self

特殊:顶级的 self

1
p self # main

p 出来的肯定是对象,为什么是 main,只需要改写其 to_s 和 inspect 方法即可,

1
2
3
4
5
6
7
8
9
10
11
12
13
o1 = {name: 'zch'}
p o1
p self
class Temp
def to_s
'temp'
end

def inspect
'temp'
end
end
p Temp.new

self 也可以是类

1
2
3
4
5
6
7
class MyClass2
p 'MyClass2'
p self # self 是类
def my_method
p self
end
end

私有方法

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass
def public_method
'MyClass::public_method'
end
private
def private_method1
'MyClass::private_method1'
end
def private_method2
'MyClass::private_method2'
end
end

使用 private 分隔私有方法,非常机智,private 以上都是不是私有的,下面都是私有的

1
2
3
4
obj = MyClass.new
obj.public_method
obj.private_methods1 # 报错
obj.private_methods2 # 报错

调用 private 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass
def public_method1
self.private_method1 # 报错
end
def public_method2
private_method1
end
private
def private_method1
'MyClass::private_method1'
end
def private_method2
'MyClass::private_method2'
end
end

私有方法调用必须满足如下情况

  1. 必须在对象所在的类中调用
  2. 不能再前面加 self 字样(只能隐式指定 self)

动态调用方法

调用方法有两种

  1. obj.xx_method(‘hi’)
  2. obj.send(:xx_method, ‘hi’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Notification
def notify(witch)
send "notify_#{witch}"
end
private
def notify_wx
p '通知到威信'
end
def notify_qq
p '通知到qq'
end
def notify_phone
p '通知到手机'
end
def notify_all
notify_wx
notify_qq
notify_phone
end
end

obj = Notification.new
obj.notify('qq')

注意 send 调用方法不分共、私有,上述可以直接 obj.send(:notify_qq, 'hi')。(ruby 中的私有方法更像是一种君子协定:),另外你也可以使用 public_send,这样就不能调用私有方法了

动态定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass
x = :methods1
define_method x do |n|
n * 3
end
end

require 'test/unit'
class MyClassTest < Test::Unit::TestCase
def test_1
obj = MyClass.new
assert_equal 6, obj.methods1(2)
end
end

动态定义的好处:可以在运行时决定方法的名字,而不是写代码时
这在批量创建方法时非常有用

自定义未定义方法(狗头)

1
2
3
class MyClass;end
obj = MyClass.new
obj.xxx

调用 obj.xxx, 一般来说调用一个未定义的方法,其他语言会直接报错,但是 ruby 不会,obj 会一直向上查找,直到调用 BaseObject#method_missing 这个方法去报错
也就等同于 obj.send(:method_missing, :xxx) ,这两个报错一模一样,即可验证

所以我们就可以直接覆盖 method_mssing

1
2
3
4
5
6
7
8
9
10
class MyClass
private def method_missing(method, *args)
p "method:#{method},参数:#{args}"
p '找不到方法啊,大哥'
end
end

obj = MyClass.new

obj.xxx(1,2,3)

这么做以后,就可以让一个对象响应任何一个方法
例如可以看看 hashie 的库,lib=>mash=>method_missing 定义了更好用的对象