arql 0.3.29 → 0.3.31

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,766 @@
1
+ * Java 开发者的 Ruby 入门简明教程
2
+
3
+ Java 非常成熟,久经检验,且非常快(与那些反对java的人可能还在声称的相反)。但它也非常啰嗦。从 Java 到Ruby,可以预见的
4
+ 是代码规模将大大缩小。你也可以期望使用相对少的时间快速出产原型。
5
+
6
+ ** 相似点 Ruby 与 Java 一样的地方
7
+
8
+ - 垃圾回收器帮你管理内存。
9
+ - 强类型对象。
10
+ - 有 public、 private 和 protected 方法。
11
+ - 拥有嵌入式文档工具(Ruby 的工具叫 rdoc)。rdoc 生成的文档与 javadoc 非常相似。
12
+
13
+ ** 相异点 Ruby 与 Java 不同的地方
14
+
15
+ 一些简单总结:
16
+
17
+ - 你不需要编译你的代码。你只需要直接运行它。
18
+ - 定义像类这样的东西时,可以使用 =end= 关键字,而不使用花括号包裹代码块。
19
+ - 使用 =require= 代替 =import= 。
20
+ - 所有成员变量为私有。在外部,使用方法获取所有你需要的一切。
21
+ - 方法调用的括号通常是可选的,经常被省略。
22
+ - 一切皆对象,包括像 2 和 3.14159 这样的数字。
23
+ - 没有静态类型检查。
24
+ - 变量名只是标签。它们没有相应的类型。
25
+ - 没有类型声明。按需分配变量名,及时可用(如: ~a = [1,2,3]~ 而不是 ~int[] a = {1,2,3};~ )。
26
+ - 没有显式转换。只需要调用方法。代码运行之前,单元测试应该告诉你出现异常。
27
+ - 使用 ~foo = Foo.new("hi")~ 创建新对象,而非 ~Foo foo = new Foo("hi")~ 。
28
+ - 构造器总是命名为“initialize” 而不是类名称。
29
+ - 作为接口的替代,你将获得“混入(mixins)”。
30
+ - 相比 XML,倾向于使用 YAML。
31
+ - =nil= 替代 =null= 。
32
+ - Ruby 对 ~==~ 和 ~equals()~ 的处理方式与 Java 不一样。测试相等性使用 ~==~ (Java 中是 ~equals()~ )。测试是否为同一
33
+ 对象使用 ~equals?()~ (Java 中是 ~==~ )。
34
+ - Ruby 里面调用一个函数或方法,参数两边的圆括号可以省略。如:
35
+ - ~p.say_hello("Hi", "Jack")~ 可以写成 ~p.say_hello "Hi", "Jack"~
36
+ - ~f.close()~ 可以写成 ~f.close~
37
+ - 定义函数或方法时,参数两边的圆括号也可以省略。
38
+ - Ruby 的类可以重新「打开」,即在原有的类定义上追加新的方法或属性、覆盖已有的方法、属性。如:
39
+ #+BEGIN_SRC ruby
40
+ class Person < ActiveRecord::Base # 定义了一个 Person 类,它是 ActiveRecord::Base 的子类
41
+ def say_hello(message, name)
42
+ puts "#{name}, #{message}"
43
+ end
44
+ end
45
+
46
+
47
+ class Person # 重新打开 Person 类,这里添加了一个 sleep 方法,并且 Person 仍然有 say_hello 方法,并且它仍然是 ActiveRecord::Base 的子类
48
+ has_many :books # has_many 是从 ActiveRecord::Base 继承来的类方法(静态方法), 类方法可以直接在类体中调用(就是和定义方法的同一层级),而实例方法则不可以在类体中调用
49
+ def sleep(time_secs)
50
+ ...
51
+ end
52
+ end
53
+ #+END_SRC
54
+
55
+ 下面是更详细的差异的介绍:
56
+
57
+ ** 快速简明教程
58
+
59
+ *** 代码块以 end 关键字结束
60
+
61
+ 不同于 Java,Ruby 使用 =end= 关键字来结束代码块,而不是使用花括号。如:
62
+
63
+ **** 类定义
64
+ #+BEGIN_SRC ruby
65
+ class Person
66
+ def initialize(name)
67
+ @name = name
68
+ end
69
+ end
70
+ #+END_SRC
71
+ **** 函数方法定义
72
+ #+BEGIN_SRC ruby
73
+ def say_hello
74
+ puts "Hello"
75
+ end
76
+ #+END_SRC
77
+ *** 一切皆对象
78
+
79
+ Ruby 中的数字、字符串、数组等都是对象,它们都有自己的方法。如:
80
+
81
+ #+BEGIN_SRC ruby
82
+ 3.times { puts "Hello" } # 输出三次 Hello
83
+ "Hello".length
84
+ [1, 2, 3].reverse
85
+ #+END_SRC
86
+
87
+ *** 基本数据类型
88
+ **** 数值
89
+ #+BEGIN_SRC ruby
90
+ 1 + 2
91
+ 2 * 3
92
+ 10 / 5
93
+ 10 % 3
94
+ #+END_SRC
95
+ **** 字符串
96
+ #+BEGIN_SRC ruby
97
+ "Hello, " + "World"
98
+ "Hello" * 3
99
+ "Hello".length
100
+ "Hello".reverse
101
+ #+END_SRC
102
+
103
+ + 单引号字符串中的特殊字符不会被转义,而双引号字符串中的特殊字符会被转义。如:
104
+ #+BEGIN_SRC ruby
105
+ puts 'Hello\nWorld' # 输出 Hello\nWorld
106
+ puts "Hello\nWorld" # 输出 Hello
107
+ # World
108
+ #+END_SRC
109
+ + 双引号字符串中可以使用 #{} 来插入变量或表达式。如:
110
+ #+BEGIN_SRC ruby
111
+ name = "Jack"
112
+ puts "Hello, #{name}" # 输出 Hello, Jack
113
+ #+END_SRC
114
+
115
+ **** nil
116
+
117
+ Ruby 中的 =nil= 相当于 Java 中的 =null= 。
118
+
119
+ **** Symbol
120
+
121
+ #+BEGIN_SRC ruby
122
+ :name
123
+ #+END_SRC
124
+
125
+ Symbol 是一种特殊的字符串(但Symbok 类的和表示字符串的 String 类没有直接关系),它的值是唯一的。Symbol 通常用来表示一个名字或标识符。
126
+
127
+ **** boolean
128
+
129
+ Ruby 中的 true 和 false 都是对象,它们都是 TrueClass 和 FalseClass 的实例。
130
+
131
+ 在 Ruby 中,除了 false 和 nil 为假,其他值都为真。
132
+
133
+ 在 Ruby 代码中,还经常看到 =if obj.present?= 等方法,这些方法是 Rails 提供的,它们是对 Ruby 的扩
134
+ 展。其中,=obj.present?= 方法会判断 obj 是否为 nil 或空字符串或空数组、空散列
135
+
136
+ **** 数组
137
+
138
+ #+BEGIN_SRC ruby
139
+ [1, 2, 3]
140
+ [1, 2, 3].length
141
+ [1, 2, 3].reverse
142
+ [1, 2, 3] << 4
143
+ #+END_SRC
144
+
145
+ + 数组中的元素可以是不同类型的对象。
146
+ + 数组中的元素可以通过索引访问,索引从 0 开始。
147
+ + 数组中的元素可以通过 << 方法添加到数组的末尾。
148
+ ***** 数组的常用方法
149
+ ****** each
150
+ each 方法用于遍历数组中的元素。如:
151
+ #+BEGIN_SRC ruby
152
+ [1, 2, 3].each { |i| puts i }
153
+ #+END_SRC
154
+ ****** map
155
+ map 方法用于对数组中的每个元素执行块中的操作,返回一个新的数组。如:
156
+ #+BEGIN_SRC ruby
157
+ [1, 2, 3].map { |i| i * 2 }
158
+ #+END_SRC
159
+ ****** select
160
+ select 方法用于从数组中选择满足条件的元素,返回一个新的数组。如:
161
+ #+BEGIN_SRC ruby
162
+ [1, 2, 3].select { |i| i > 1 }
163
+ #+END_SRC
164
+ ****** reduce
165
+ reduce 方法用于对数组中的元素进行累加。如:
166
+ #+BEGIN_SRC ruby
167
+ [1, 2, 3].reduce { |sum, i| sum + i }
168
+ #+END_SRC
169
+ ****** each_with_index
170
+ each_with_index 方法用于遍历数组中的元素,同时获取元素的索引。如:
171
+ #+BEGIN_SRC ruby
172
+ [1, 2, 3].each_with_index { |i, index| puts "#{index}: #{i}" }
173
+ #+END_SRC
174
+
175
+ ****** each_with_object
176
+ each_with_object 方法用于遍历数组中的元素,同时传递一个对象。如:
177
+ #+BEGIN_SRC ruby
178
+ [person1, person2].each_with_object({}) { |person, hash| hash[person.name] = person.age }
179
+ #+END_SRC
180
+ ****** group_by
181
+ group_by 方法用于根据块中的条件对数组中的元素进行分组。如:
182
+ #+BEGIN_SRC ruby
183
+ [person1, person2].group_by { |person| person.gender }
184
+ #+END_SRC
185
+ ****** in_groups
186
+ in_groups 方法用于将数组分成若干组。如:
187
+ #+BEGIN_SRC ruby
188
+ [1, 2, 3, 4, 5].in_groups(2)
189
+ #+END_SRC
190
+ ****** in_groups_of
191
+ in_groups_of 方法用于将数组分成若干组,每组包含指定个数的元素。如:
192
+ #+BEGIN_SRC ruby
193
+ [1, 2, 3, 4, 5].in_groups_of(2)
194
+ #+END_SRC
195
+ **** 哈希
196
+
197
+ 哈希是一种键值对的数据结构,类似于 Java 中的 Map。如:
198
+
199
+ #+BEGIN_SRC ruby
200
+ { "name" => "Jack", "age" => 20 }
201
+ { :name => "Jack", :age => 20 }
202
+ { name: "Jack", age: 20 }
203
+ #+END_SRC
204
+
205
+ 上述代码中:
206
+ 1. 第一行的哈希中的键和值都是字符串。
207
+ 2. 第二行的哈希中的键是 Symbol,值是字符串。
208
+ 3. 第三行的哈希中的键是 Symbol,值是字符串,也就是说在一个哈希中, =key: value= 的形式等价于 ~:key => value~ 的形式。
209
+
210
+ 4. 哈希是一种键值对的集合。
211
+ 5. 哈希中的键和值可以是任意类型的对象。
212
+ 6. 哈希中的键是唯一的。
213
+ ***** 哈希的常用方法
214
+ ****** each
215
+ each 方法用于遍历哈希中的键值对。如:
216
+ #+BEGIN_SRC ruby
217
+ { name: "Jack", age: 20 }.each { |key, value| puts "#{key}: #{value}" }
218
+ #+END_SRC
219
+ ****** map
220
+ map 方法用于对哈希中的每个键值对执行块中的操作,返回一个新的数组。如:
221
+ #+BEGIN_SRC ruby
222
+ { name: "Jack", age: 20 }.map { |key, value| value }
223
+ #+END_SRC
224
+ ****** select
225
+ select 方法用于从哈希中选择满足条件的键值对,返回一个新的哈希。如:
226
+ #+BEGIN_SRC ruby
227
+ { name: "Jack", age: 20 }.select { |key, value| value > 18 }
228
+ #+END_SRC
229
+ ****** keys
230
+ keys 方法用于获取哈希中的所有键。如:
231
+ #+BEGIN_SRC ruby
232
+ { name: "Jack", age: 20 }.keys
233
+ #+END_SRC
234
+ ****** values
235
+ values 方法用于获取哈希中的所有值。如:
236
+ #+BEGIN_SRC ruby
237
+ { name: "Jack", age: 20 }.values
238
+ #+END_SRC
239
+ ****** merge
240
+ merge 方法用于合并两个哈希。如:
241
+ #+BEGIN_SRC ruby
242
+ { name: "Jack" }.merge({ age: 20 })
243
+ #+END_SRC
244
+ ****** merge!
245
+ merge! 方法用于将另一个哈希合并到当前哈希中。如:
246
+ #+BEGIN_SRC ruby
247
+ { name: "Jack" }.merge!({ age: 20 })
248
+ #+END_SRC
249
+ *** 类的基础知识
250
+ **** 初始化函数
251
+
252
+ 初始化函数相当于 Java 中的构造函数,它是在创建对象时自动调用的函数。在 Ruby 中,初始化函数的名字是 =initialize=
253
+
254
+ #+BEGIN_SRC ruby
255
+ class Person
256
+ def initialize(name, age)
257
+ @name = name
258
+ @age = age
259
+ end
260
+ end
261
+ #+END_SRC
262
+ **** 属性和 getter/setter
263
+
264
+ 上例中的 =@name= 和 =@age= 是 Person 类的属性,它们是实例变量,只能在类的内部访问。如果要在类的外部访问这两个属性,需要提供 getter 和 setter 方法。如:
265
+
266
+ #+BEGIN_SRC ruby
267
+ class Person
268
+ def initialize(name, age)
269
+ @name = name
270
+ @age = age
271
+ end
272
+
273
+ def name
274
+ @name
275
+ end
276
+
277
+ def name=(name)
278
+ @name = name
279
+ end
280
+ end
281
+
282
+ p = Person.new("Jack", 20)
283
+ puts p.name # 这是调用 getter 方法, 而不是直接访问实例变量
284
+ p.name = "Tom" # 这是调用 setter 方法, 而不是直接设置实例变量
285
+ #+END_SRC
286
+
287
+ 上述代码中, =name= 方法是 getter 方法, =name= 方法是 setter 方法。Ruby 提供了一种更简洁的方式来定义 getter 和 setter 方法,如:
288
+
289
+ #+BEGIN_SRC ruby
290
+ class Person
291
+ attr_accessor :name
292
+
293
+ def initialize(name, age)
294
+ @name = name
295
+ @age = age
296
+ end
297
+ end
298
+ #+END_SRC
299
+
300
+ 如果只需要 getter 方法,可以使用 =attr= 活 =attr_reader= 方法;如果只需要 setter 方法,可以使用 =attr_writer= 方法。
301
+
302
+ #+BEGIN_SRC ruby
303
+ class Person
304
+ attr :name
305
+ attr_writer :name
306
+
307
+ def initialize(name, age)
308
+ @name = name
309
+ @age = age
310
+ end
311
+ end
312
+ #+END_SRC
313
+
314
+ **** 继承
315
+
316
+ 和 Java 一样,Ruby 也是只支持单继承的。Ruby 使用 =<= 操作符来表示继承关系。如:
317
+
318
+ #+BEGIN_SRC ruby
319
+ class Person
320
+ def initialize(name, age)
321
+ @name = name
322
+ @age = age
323
+ end
324
+ end
325
+
326
+ class Student < Person
327
+ def initialize(name, age, school)
328
+ super(name, age)
329
+ @school = school
330
+ end
331
+ end
332
+ #+END_SRC
333
+
334
+ **** 静态方法
335
+
336
+ Ruby 中的静态方法使用 =self= 关键字来定义。如:
337
+
338
+ #+BEGIN_SRC ruby
339
+ class Person
340
+ def initialize(name, age)
341
+ @name = name
342
+ @age = age
343
+ end
344
+
345
+ def self.say_hello
346
+ puts "Hello"
347
+ end
348
+ end
349
+ #+END_SRC
350
+
351
+ 上述代码中, =say_hello= 是一个静态方法,可以通过 Person 类直接调用。 还有另一种常用的定义静态方法的方式,如:
352
+
353
+ #+BEGIN_SRC ruby
354
+ class Person
355
+ def initialize(name, age)
356
+ @name = name
357
+ @age = age
358
+ end
359
+
360
+ class << self
361
+ def say_hello
362
+ puts "Hello"
363
+ end
364
+ end
365
+ end
366
+ #+END_SRC
367
+
368
+ *** 类可以重新打开
369
+
370
+ Ruby 的类可以重新「打开」,即在原有的类定义上追加新的方法或属性、覆盖已有的方法、属性。如:
371
+
372
+ #+BEGIN_SRC ruby
373
+ class Person < ActiveRecord::Base # 定义了一个 Person 类,它是 ActiveRecord::Base 的子类
374
+ def say_hello(message, name)
375
+ puts "#{name}, #{message}"
376
+ end
377
+ end
378
+
379
+
380
+ class Person # 重新打开 Person 类,这里添加了一个 sleep 方法,并且 Person 仍然有 say_hello 方法,并且它仍然是 ActiveRecord::Base 的子类
381
+ has_many :books # has_many 是从 ActiveRecord::Base 继承来的类方法(静态方法), 类方法可以直接在类体中调用(就是和定义方法的同一层级),而实例方法则不可以在类体中调用
382
+ def sleep(time_secs)
383
+ ...
384
+ end
385
+ end
386
+ #+END_SRC
387
+
388
+ *** 混入和 Enumerable
389
+
390
+ Ruby 虽然和 Java 一样只支持单继承,但是 Ruby 提供了一种叫做「混入」的机制,可以在类中引入模块(module),从而实现多继承的效果。如:
391
+
392
+ #+BEGIN_SRC ruby
393
+ module MyModule
394
+ def say_hello
395
+ puts "Hello"
396
+ end
397
+ end
398
+
399
+ class Person
400
+ def xxx
401
+ end
402
+ end
403
+
404
+ class Student < Person
405
+ include MyModule
406
+ end
407
+ #+END_SRC
408
+
409
+ 现在 Student 类既继承了 Person 类的 xxx 方法,又引入了 MyModule 模块的 say_hello 方法。
410
+
411
+ 因为模块(module关键字定义的部分)和类不同的是,模块不能被实例化,也就是不能创建模块的对象。所以混入模块,只会继承模块的方法,而不会继承模块的属性,从而避免了多继承的问题。
412
+
413
+ Ruby 标准库中的 Enumerable 模块就是一个很好的例子。Enumerable 模块提供了很多方法,如 =each=、=map=、=select=、=reject=、=detect=、=sort= 等,这些方法可以被任何实现了 =each= 方法的类包含进来,从而实现了类似于 Java 中的 Collection 接口的效果。如:
414
+
415
+ #+BEGIN_SRC ruby
416
+ class MyCollection
417
+ include Enumerable
418
+ def each
419
+ ...
420
+ end
421
+ end
422
+ #+END_SRC
423
+
424
+ 现在 MyCollection 类就可以像数组一样使用 =map=、=select=、=reject=、=detect=、=sort= 等方法了。而数组实际上也是实现了 =each= 方法并混入了 Enumerable 模块的类。
425
+
426
+ *** Kernel 模块
427
+
428
+ Ruby 中的 Kernel 模块是一个特殊的模块,它包含了很多常用的方法,这些方法可以直接在任何地方调用,而不需要通过模块名或类名来调用。如:
429
+
430
+ #+BEGIN_SRC ruby
431
+ puts "Hello"
432
+ sleep 1
433
+ #+END_SRC
434
+
435
+ 上述代码中的 =puts= 和 =sleep= 方法都是 Kernel 模块中的方法,可以直接调用。
436
+
437
+ 事实上,Ruby 中的很多方法都是定义在 Kernel 模块中的,这是因为 Ruby 中的所有类都是 Object 类的子类,而 Object 类又混入了 Kernel 模块,所以所有的类都可以直接调用 Kernel 模块中的方法。
438
+
439
+ 如果你希望定义一个全局方法,可以直接定义在 Kernel 模块中,这样就可以在任何地方调用了。如:
440
+
441
+ #+BEGIN_SRC ruby
442
+ module Kernel
443
+ def say_hello
444
+ puts "Hello"
445
+ end
446
+ end
447
+
448
+ say_hello
449
+ #+END_SRC
450
+
451
+ *** Block 和 Proc
452
+
453
+ Java 8 中引入了 Lambda 表达式,Ruby 中的 Block 和 Proc 与之类似,都是相当于一个函数对象。
454
+
455
+ **** Block
456
+
457
+ Block 并不是一个对象,而是一段代码块,是原生的语法元素、可以在方法调用时传递给方法。如:
458
+
459
+ #+BEGIN_SRC ruby
460
+ def say_hello
461
+ puts "Hello"
462
+ yield if block_given? # block_given? 用于判断在调用这个函数时,是否使用了 block, yield 会执行传递给 say_hello 方法的 Block
463
+ end
464
+
465
+ say_hello # 输出 "Hello"
466
+
467
+ say_hello do
468
+ puts "World"
469
+ end # 输出 "Hello" 和 "World"
470
+ #+END_SRC
471
+
472
+ 也可以使用 花括号 代替 do/end:
473
+
474
+ #+BEGIN_SRC ruby
475
+ say_hello { puts "World" } # 输出 "Hello" 和 "World"
476
+ #+END_SRC
477
+
478
+ **** Proc
479
+
480
+ proc 看起来和 Block 类似,但是 proc 是一个对象,可以赋值给变量,也可以作为参数传递给方法。如:
481
+
482
+ #+BEGIN_SRC ruby
483
+ my_proc = Proc.new { puts "Hello" }
484
+ my_proc.call # 输出 "Hello"
485
+
486
+ def say_hello(block)
487
+ block.call
488
+ end
489
+
490
+ say_hello(my_proc) # 输出 "Hello"
491
+ #+END_SRC
492
+
493
+ ***** 将 Block 转换为 Proc
494
+
495
+ 有时候我们需要将 Block 转换为 Proc 对象,可以使用 & 符号:
496
+
497
+ #+BEGIN_SRC ruby
498
+ def say_hello(&blk)
499
+ blk.call
500
+ end
501
+
502
+ say_hello { puts "Hello" } # 输出 "Hello"
503
+ #+END_SRC
504
+
505
+ 上面的代码中,&blk 会将传递给 say_hello 方法的 Block 转换为 Proc 对象。
506
+
507
+
508
+ ***** 其他定义 Proc 的方式
509
+ ****** lambda
510
+
511
+ lambda 是 Proc 的一种特殊形式,可以使用 lambda 方法定义一个 Proc 对象:
512
+
513
+ #+BEGIN_SRC ruby
514
+ my_proc = lambda { puts "Hello" }
515
+ my_proc.call # 输出 "Hello"
516
+ #+END_SRC
517
+
518
+ lambda 定义的 Proc 和普通的 Proc 的区别在于,lambda 会检查传递给它的参数个数是否正确,而普通的 Proc 不会。如:
519
+
520
+ #+BEGIN_SRC ruby
521
+ my_proc = Proc.new { |a, b| puts a + b }
522
+ my_proc.call(1) # 输出 1
523
+
524
+ my_proc = lambda { |a, b| puts a + b }
525
+ my_proc.call(1) # 报错
526
+ ****** ->
527
+
528
+ -> 是 lambda 的一种简写形式,可以用来定义一个 Proc 对象:
529
+
530
+ #+BEGIN_SRC ruby
531
+ my_proc = -> { puts "Hello" }
532
+ my_proc.call # 输出 "Hello"
533
+ #+END_SRC
534
+ ***** =&:my_method=
535
+
536
+ 有时候我们会看到这样的写法:
537
+
538
+ #+BEGIN_SRC ruby
539
+ p1 = Person.new
540
+ p2 = Person.new
541
+
542
+ [p1, p2].each(&:say_hello)
543
+ #+END_SRC
544
+
545
+ 这里的 =&:say_hello= 实际上是将 =:say_hello= Symbol对象转换为 Proc 对象,然后传递给 each 方法。这种写法等价于:
546
+
547
+ #+BEGIN_SRC ruby
548
+ [p1, p2].each { |p| p.say_hello }
549
+ #+END_SRC
550
+
551
+ 这种用法看起来很像 Java 8 中的方法引用,但实际上是 Ruby 的一个语法糖。
552
+
553
+ 那么这是如何将 Symbol 对象转换为 Proc 对象呢?实际上 =&= 后面跟一个对象,就是调用这个对象的 =to_proc= 方法,将其转换为 Proc 对象。如:
554
+
555
+ =&:say_hello= 就是将调用 =:say_hello= 这个 Symbol 对象的 to_proc 方法,而 Symbol 的 =to_proc= 方法是这样定义的:
556
+
557
+ #+BEGIN_SRC ruby
558
+ class Symbol
559
+ def to_proc
560
+ Proc.new { |obj, *args| obj.send(self, *args) }
561
+ end
562
+ end
563
+ #+END_SRC
564
+
565
+ *** 元编程
566
+
567
+ 元编程是指在运行时动态地创建类和方法,或者修改现有类和方法的技术。Ruby 是一种动态语言,非常适合进行元编程。
568
+
569
+ 可以理解为有点像 Java 中的反射机制,但是 Ruby 的元编程更加强大和灵活。
570
+
571
+ Ruby 提供了一些方法来动态地定义类和方法,如 =method_missing=, =define_method=, =class_eval=, =instance_eval= 等。
572
+
573
+ 通过元编程,我们可以实现很多功能,如动态地创建类和方法,动态地修改类和方法,动态地调用方法等。
574
+
575
+ 虽然你在编码时有可能不会用到元编程,但是了解元编程的原理和技术,可以帮助你更好地理解 Rails 等 Ruby 库或者框架的特性和机制。
576
+
577
+ 更多关于元编程的内容,可以参考 「Ruby 元编程」 这本书。
578
+
579
+ **** =method_missing=
580
+
581
+ =method_missing= 是 Ruby 中一个非常重要的方法,当调用一个对象不存在的方法时,Ruby 会调用这个对象的 =method_missing= 方法。
582
+
583
+ 通过重写 =method_missing= 方法,我们可以实现很多功能,如动态地创建方法,动态地调用方法等。
584
+
585
+ 例如,我们可以定义一个类,当调用这个类的不存在的方法时,输出方法名:
586
+
587
+ #+BEGIN_SRC ruby
588
+ class MyClass
589
+ def method_missing(name, *args)
590
+ puts "You called #{name} with #{args.inspect}"
591
+ end
592
+ end
593
+
594
+ obj = MyClass.new
595
+ obj.hello(1, 2, 3) # 输出 "You called hello with [1, 2, 3]"
596
+ #+END_SRC
597
+
598
+ 那么 =method_missing= 方法有哪些实际用途呢?考虑我们定义一个 ActiveRecord 模型:
599
+
600
+ #+BEGIN_SRC ruby
601
+ class User < ActiveRecord::Base
602
+ end
603
+ #+END_SRC
604
+
605
+ ActiveRecord 库会根据约定的命名规则,将 User 类和数据库中的 users 表对应起来,当我们调用 User 类的一些方法:
606
+
607
+ #+BEGIN_SRC ruby
608
+ user = User.find(1)
609
+ user.username = "Tom"
610
+ #+END_SRC
611
+
612
+ 那么 =user.username= setter 方法是如何实现的呢?如果我们是 ActiveRecord 库的作者,我们就可以使用 =method_missing= 方法来实现这些方法:
613
+
614
+ #+BEGIN_SRC ruby
615
+ class ActiveRecord::Base
616
+ def method_missing(name, *args)
617
+ if db_columns.include_by_name?(name) # 判断数据库中是否有这个字段
618
+ db_columns.write_by_column_name(name, args.first) # 写入数据库
619
+ else
620
+ super # 调用父类的 method_missing 方法
621
+ end
622
+ end
623
+ end
624
+ #+END_SRC
625
+
626
+ **** =define_method=
627
+
628
+ =define_method= 方法可以动态地定义方法,它接受一个方法名和一个块,然后定义一个方法。
629
+
630
+ 例如,我们可以定义一个类,动态地定义一个方法:
631
+
632
+ #+BEGIN_SRC ruby
633
+ class MyClass
634
+ define_method :hello do |name|
635
+ puts "Hello, #{name}!"
636
+ end
637
+ end
638
+
639
+ obj = MyClass.new
640
+ obj.hello("Tom") # 输出 "Hello, Tom!"
641
+ #+END_SRC
642
+
643
+ 和 =method_missing= 方法不同, =define_method= 方法是在类对象上调用的,而且定义好之后,这个方法就会一直存在,直到这个类被销毁。而 =method_missing= 方法是在实例对象上调用的,
644
+ 并且每次调用不存在的方法时,都会调用 =method_missing= 方法。
645
+
646
+ 在 =method_missing= 的那个示例中,考虑到性能问题,不希望每次调用 ~user.username=~ 的时候都调用 =method_missing= 方法,我们可以使用 =define_method= 方法来定义这个方法:
647
+
648
+ #+BEGIN_SRC ruby
649
+ class ActiveRecord::Base
650
+ def method_missing(name, *args)
651
+ if db_columns.include_by_name?(name) # 判断数据库中是否有这个字段
652
+ self.class.send(:define_method, name) do |value|
653
+ db_columns.write_by_column_name(name, value) # 写入数据库
654
+ end
655
+ send(name, args.first) # 调用刚刚定义的方法, 而且这样一来,下次调用这个方法就不会再调用 method_missing 方法了
656
+ else
657
+ super # 调用父类的 method_missing 方法
658
+ end
659
+ end
660
+ end
661
+ #+END_SRC
662
+
663
+ **** =class_eval=
664
+
665
+ =class_eval= 方法可以动态地定义类的方法,它接受一个字符串作为参数,然后定义一个方法。
666
+
667
+ 例如,我们可以定义一个类,动态地定义一个方法:
668
+
669
+ #+BEGIN_SRC ruby
670
+ class MyClass
671
+ class_eval %{
672
+ def hello(name)
673
+ puts "Hello, \#{name}!"
674
+ end
675
+ }
676
+ end
677
+
678
+ obj = MyClass.new
679
+ obj.hello("Tom") # 输出 "Hello, Tom!"
680
+ #+END_SRC
681
+
682
+ **** =instance_eval=
683
+
684
+ =instance_eval= 方法可以动态地定义实例对象的方法,它接受一个字符串作为参数,然后定义一个方法。
685
+
686
+ 例如,我们可以定义一个类,动态地定义一个方法:
687
+
688
+ #+BEGIN_SRC ruby
689
+ class MyClass
690
+ def initialize(name)
691
+ @name = name
692
+ end
693
+ end
694
+
695
+ obj = MyClass.new("Tom")
696
+ obj.instance_eval %{
697
+ def hello
698
+ puts "Hello, \#{@name}!"
699
+ end
700
+ }
701
+
702
+ obj.hello # 输出 "Hello, Tom!"
703
+
704
+ obj2 = MyClass.new("Jerry")
705
+ obj2.hello # 报错, 因为 obj2 没有定义 hello 方法, 请自行搜索学习 Ruby 中的 singleton class 的概念
706
+ #+END_SRC
707
+
708
+ **** =instance_exec=
709
+
710
+ =instance_exec= 方法和 =instance_eval= 方法类似,但是它可以接受块作为参数。
711
+
712
+ 例如,我们可以定义一个类,动态地定义一个方法:
713
+
714
+ #+BEGIN_SRC ruby
715
+ class MyClass
716
+ def initialize(name)
717
+ @name = name
718
+ end
719
+ end
720
+
721
+ obj = MyClass.new("Tom")
722
+ obj.instance_exec("Jerry") do |name|
723
+ puts "Hello, #{name}!"
724
+ end # 输出 "Hello, Jerry!", 这里定义的仍然是一个 singleton method
725
+ #+end_src
726
+
727
+
728
+ 还可以借助 =instance_exec= 方法来动态定义一个普通的实例方法:
729
+
730
+ #+BEGIN_SRC ruby
731
+ class MyClass
732
+ def initialize(name)
733
+ @name = name
734
+ end
735
+ end
736
+
737
+ MyClass.instance_exec do
738
+ define_method :hello do
739
+ puts "Hello, #{@name}!"
740
+ end
741
+ end
742
+
743
+ obj = MyClass.new("Tom")
744
+ obj.hello # 输出 "Hello, Tom!"
745
+ #+END_SRC
746
+
747
+
748
+
749
+
750
+ **** =Class.new=
751
+
752
+ 示例:
753
+
754
+ #+BEGIN_SRC ruby
755
+ Employee = Class.new(Person) do
756
+ def hello
757
+ ...
758
+ end
759
+ end
760
+
761
+ e = Employee.new
762
+ e.hello
763
+ #+END_SRC
764
+
765
+ Person 成为 Employee 的父类
766
+