-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathchapter04.html
593 lines (442 loc) · 36.5 KB
/
chapter04.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
<!DOCTYPE HTML>
<html lang="en-US" manifest="./manifest.appcache">
<head prefix="og: http://ogp.me/ns# book: http://ogp.me/ns/book#">
<meta charset="UTF-8">
<title>实用元编程方法 | Ruby中的元编程</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="robots" content="index, follow">
<meta name="author" content="">
<meta name="description" content=" 本系列翻译自Ruby Metaprogramming站点上的课程笔记,并加入了我(DeathKing)的一些个人演绎、资料补充等。希望对大家有所帮助。">
<meta name="keywords" content="gitbook,github" >
<meta name="generator" content="www.gitbook.io">
<link rel="next" href="./chapter05.html" />
<link rel="prev" href="./chapter03.html" />
<meta property="og:title" content="实用元编程方法 | Ruby中的元编程">
<meta property="og:site_name" content="Ruby中的元编程">
<meta property="og:type" content="book">
<meta property="og:locale" content="en_US">
<meta property="book:author" content="https://github.com/">
<meta property="book:tag" content="GitBook">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">
</head>
<body>
<link rel="stylesheet" href="gitbook/style.css">
<div class="book" data-level="4" data-basepath="." data-revision="1401444078312">
<div class="book-header">
<!-- Actions Left -->
<a href="#" class="btn pull-left toggle-summary" aria-label="Toggle summary"><i class="fa fa-align-justify"></i></a>
<a href="https://github.com/null" target="_blank" class="btn pull-left home-bookmark" aria-label="GitHub home"><i class="fa fa-bookmark-o"></i></a>
<a href="#" class="btn pull-left toggle-search" aria-label="Toggle search"><i class="fa fa-search"></i></a>
<span id="font-settings-wrapper">
<a href="#" class="btn pull-left toggle-font-settings" aria-label="Toggle font settings"><i class="fa fa-font"></i>
</a>
<div class="dropdown-menu font-settings">
<div class="dropdown-caret">
<span class="caret-outer"></span>
<span class="caret-inner"></span>
</div>
<div class="btn-group btn-block">
<button id="reduce-font-size" class="btn btn-default">A</button>
<button id="enlarge-font-size" class="btn btn-default">A</button>
</div>
<ul class="list-group font-family-list">
<li class="list-group-item" data-font="0">Serif</li>
<li class="list-group-item" data-font="1">Sans</li>
</ul>
<div class="btn-group btn-group-xs btn-block color-theme-list">
<button type="button" class="btn btn-default" id="color-theme-preview-0" data-theme="0">White</button>
<button type="button" class="btn btn-default" id="color-theme-preview-1" data-theme="1">Sepia</button>
<button type="button" class="btn btn-default" id="color-theme-preview-2" data-theme="2">Night</button>
</div>
</div>
</span>
<!-- Actions Right -->
<a href="#" target="_blank" class="btn pull-right google-plus-sharing-link sharing-link" data-sharing="google-plus" aria-label="Share on Google Plus"><i class="fa fa-google-plus"></i></a>
<a href="#" target="_blank" class="btn pull-right facebook-sharing-link sharing-link" data-sharing="facebook" aria-label="Share on Facebook"><i class="fa fa-facebook"></i></a>
<a href="#" target="_blank" class="btn pull-right twitter-sharing-link sharing-link" data-sharing="twitter" aria-label="Share on Twitter"><i class="fa fa-twitter"></i></a>
<!-- Title -->
<h1>
<i class="fa fa-spinner fa-spin"></i>
<a href="./" >Ruby中的元编程</a>
</h1>
</div>
<div class="book-summary">
<div class="book-search">
<input type="text" placeholder="Search" class="form-control" />
</div>
<ul class="summary">
<li data-level="0" data-path="index.html">
<a href="./"><i class="fa fa-check"></i> Introduction</a>
</li>
<li class="chapter " data-level="1" data-path="chapter01.html">
<a href="./chapter01.html">
<i class="fa fa-check"></i> <b>1.</b> 前言
</a>
</li>
<li class="chapter " data-level="2" data-path="chapter02.html">
<a href="./chapter02.html">
<i class="fa fa-check"></i> <b>2.</b> 实例变量、方法、类
</a>
</li>
<li class="chapter " data-level="3" data-path="chapter03.html">
<a href="./chapter03.html">
<i class="fa fa-check"></i> <b>3.</b> 方法的调用
</a>
</li>
<li class="chapter " data-level="4" data-path="chapter04.html">
<a href="./chapter04.html">
<i class="fa fa-check"></i> <b>4.</b> 实用元编程方法
</a>
</li>
<li class="chapter " data-level="5" data-path="chapter05.html">
<a href="./chapter05.html">
<i class="fa fa-check"></i> <b>5.</b> 绑定
</a>
</li>
<li class="chapter " data-level="6" data-path="chapter06.html">
<a href="./chapter06.html">
<i class="fa fa-check"></i> <b>6.</b> 块和绑定
</a>
</li>
<li class="chapter " data-level="7" data-path="chapter07.html">
<a href="./chapter07.html">
<i class="fa fa-check"></i> <b>7.</b> 元编程实战
</a>
</li>
<li class="chapter " data-level="8" data-path="chapter08.html">
<a href="./chapter08.html">
<i class="fa fa-check"></i> <b>8.</b> 结语
</a>
</li>
<li class="divider"></li>
<li>
<a href="http://www.gitbook.io/" target="blank" class="gitbook-link">Generated using GitBook</a>
</li>
</ul>
</div>
<div class="book-body">
<div class="body-inner">
<div class="page-wrapper" tabindex="-1">
<div class="book-progress">
<div class="bar">
<div class="inner" style="width: 50%;min-width: 37.5%;"></div>
</div>
<div class="chapters">
<a href="./index.html" title="Introduction" class="chapter done new-chapter" data-progress="0" style="left: 0%;"></a>
<a href="./chapter01.html" title="前言" class="chapter done new-chapter" data-progress="1" style="left: 12.5%;"></a>
<a href="./chapter02.html" title="实例变量、方法、类" class="chapter done new-chapter" data-progress="2" style="left: 25%;"></a>
<a href="./chapter03.html" title="方法的调用" class="chapter done new-chapter" data-progress="3" style="left: 37.5%;"></a>
<a href="./chapter04.html" title="实用元编程方法" class="chapter done new-chapter" data-progress="4" style="left: 50%;"></a>
<a href="./chapter05.html" title="绑定" class="chapter new-chapter" data-progress="5" style="left: 62.5%;"></a>
<a href="./chapter06.html" title="块和绑定" class="chapter new-chapter" data-progress="6" style="left: 75%;"></a>
<a href="./chapter07.html" title="元编程实战" class="chapter new-chapter" data-progress="7" style="left: 87.5%;"></a>
<a href="./chapter08.html" title="结语" class="chapter new-chapter" data-progress="8" style="left: 100%;"></a>
</div>
</div>
<div class="page-inner">
<section class="normal" id="section-gitbook_8">
<h1 id="-">实用元编程方法</h1>
<p> 本章节将介绍一系列的元编程实用方法,使读者对元编程有一个更为具体的认识。其中一些技术,诸如反射机制,已经有很多文章介绍过了,读者可以根据自身的情况进行选择是否跳过。</p>
<h2 id="-">内省、反射</h2>
<blockquote>
<p> 一说编写元程序的语言称之为元语言。被操纵的程序的语言称之为目标语言。一门编程语言同时也是自身的元语言的能力称之为反射或者自反。</p>
<p>——摘自维基百科<a href="http://zh.wikipedia.org/wiki/%E5%85%83%E7%BC%96%E7%A8%8B" target="_blank">元编程</a>条目</p>
</blockquote>
<p> 在Ruby中,你完全有能力在运行时查看类或对象的信息。我们可以使用<code>class</code>、 <code>instance_methods</code>、 <code>intance_variables</code>等方法来达到目的。我们将这种技术称为<strong>内省(Introspection)</strong>或者<strong>反射(Reflection)</strong>。请考虑下面的代码:</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>what_does_he_do
<span class="hljs-variable">@person</span> = <span class="hljs-string">'A Rubyist'</span>
<span class="hljs-string">'Ruby programming'</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
an_object = <span class="hljs-constant">Rubyist</span>.new
puts an_object.<span class="hljs-keyword">class</span> <span class="hljs-comment"># => Rubyist</span>
puts an_object.<span class="hljs-keyword">class</span>.instance_methods(<span class="hljs-keyword">false</span>) <span class="hljs-comment"># => what_does_he_do</span>
an_object.what_does_he_do
puts an_object.instance_variables <span class="hljs-comment"># => <span class="hljs-yardoctag">@person</span></span>
</code></pre>
<p> <code>respond_to?</code>方法是反射机制中另一个有用的方法。使用<code>respond_to?</code>方法,可以提前知道对象是否能够处理你想要交予它执行的信息。所有的对象都有此方法,使用<code>respond_to?</code>方法,你可以确定对象是否能使用指定的方法。</p>
<pre><code class="lang-ruby">obj = <span class="hljs-constant">Object</span>.new
<span class="hljs-keyword">if</span> obj.respond_to?(<span class="hljs-symbol">:program</span>)
obj.program
<span class="hljs-keyword">else</span>
puts <span class="hljs-string">"Sorry, the object doesn't understand the 'program' message."</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h2 id="send">send</h2>
<p> <code>send</code>是<code>Object</code>类的实例方法。<code>send</code>方法的第一个参数是你期望对象执行的方法的名称。可以是一个<strong>字符串(String)</strong>或者<strong>符号(Symbol)</strong>,但是我们更喜欢使用符号。剩余的参数就直接传递给所指定的方法。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>welcome(*args)
<span class="hljs-string">"Welcome "</span> + args.join(<span class="hljs-string">' '</span>)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new
puts(obj.send(<span class="hljs-symbol">:welcome</span>, <span class="hljs-string">"famous"</span>, <span class="hljs-string">"Rubyists"</span>)) <span class="hljs-comment"># => Welcome famous Rubyists</span>
</code></pre>
<p> 使用<code>send</code>方法,你所想要调用的方法就顺理成章的变成了一个普通的参数。你可以在运行时,直至最后一刻自由决定到底调用哪个方法。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-keyword">end</span>
rubyist = <span class="hljs-constant">Rubyist</span>.new
<span class="hljs-keyword">if</span> rubyist.respond_to?(<span class="hljs-symbol">:also_railist</span>)
puts rubyist.send(<span class="hljs-symbol">:also_railist</span>)
<span class="hljs-keyword">else</span>
puts <span class="hljs-string">"No such information available"</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p> 上述代码展示了如果<code>rubyist</code>对象知道如何处理<code>also_railist</code>方法,那么他将会进行处理。</p>
<p> 你可以通过<code>send</code>方法调用任何方法,即使是私有方法。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
private
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>say_hello(name)
<span class="hljs-string">"<span class="hljs-subst">#{name}</span> rocks!!"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new
puts obj.send(<span class="hljs-symbol">:say_hello</span>, <span class="hljs-string">'Matz'</span>)
</code></pre>
<h2 id="define_method">define_method</h2>
<p> Module#define_method是Module类实例的私有方法。因此define_method方法仅能由类或者模块使用。你可以通过define_method动态的在receiver中定义实例方法。而你仅需要传递需要定义的方法的名字,以及一个代码块(block),就如下面演示的那样:</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
define_method <span class="hljs-symbol">:hello</span> <span class="hljs-keyword">do</span> |my_arg|
my_arg
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new
puts(obj.hello(<span class="hljs-string">'Matz'</span>)) <span class="hljs-comment"># => Matz</span>
</code></pre>
<h2 id="method_missing">method_missing</h2>
<p> 当Ruby使用look-up机制找寻方法时,如果方法不存在,那么Ruby将会在原receiver中自行调用一个叫做<code>method_missing</code>的方法。<code>method_missing</code>方法会以符号的形式传递被调用的那个不存在的方法的名字,以数组的形式传递调用时的参数,以及原调用中传递的块。<code>method_missing</code>是由<code>Kernel</code>模块提供的方法,因此任意对象都有此方法。<code>Kernel#method_missing</code>方法能响应<code>NoMethodError</code>错误。重载<code>method_missing</code>方法允许你对不存在的方法进行处理。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>method_missing(m, *args, &block)
puts <span class="hljs-string">"Called <span class="hljs-subst">#{m}</span> with <span class="hljs-subst">#{args.inspect}</span> and <span class="hljs-subst">#{block}</span>"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
<span class="hljs-constant">Rubyist</span>.new.anything <span class="hljs-comment"># => Called anything with [] and</span>
<span class="hljs-constant">Rubyist</span>.new.anything(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>) { something } <span class="hljs-comment"># => Called anything with [3, 4] and #<Proc:0x02efd664<span class="hljs-yardoctag">@tmp</span>2.rb:7></span>
</code></pre>
<p> 关于<code>method_missing</code>,<a href="http://ihower.tw/blog/about" target="_blank">ihower</a>给出了一个漂亮的例子:</p>
<pre><code class="lang-ruby">car = <span class="hljs-constant">Car</span>.new
car.go_to_taipei
<span class="hljs-comment"># go to taipei</span>
car.go_to_shanghai
<span class="hljs-comment"># go to shanghai</span>
car.go_to_japan
<span class="hljs-comment"># go to japan</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Car</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>go(place)
puts <span class="hljs-string">"go to <span class="hljs-subst">#{place}</span>"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>method_missing(name, *args)
<span class="hljs-keyword">if</span> name.to_s =~ <span class="hljs-regexp">/^go_to_(.*)/</span>
go(<span class="hljs-variable">$1</span>)
<span class="hljs-keyword">else</span>
<span class="hljs-keyword">super</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p> 这个例子出自于他在Ruby Conf China 2010上的讲义<a href="http://ihower.tw/blog/archives/4797" target="_blank">《如何设计出漂亮的Ruby API》</a>中,你能够在他的个人博客中看到相关介绍,不过这个例子只出现在<a href="http://www.slideshare.net/ihower/designing-ruby-apis" target="_blank">讲义的幻灯片</a>中。</p>
<blockquote>
<p><strong>注意</strong></p>
<p><code>method_missing</code>方法的效率不甚理想,对效率敏感的项目尽量要避免使用此方法。尽管<code>method_missing</code>的确很强力。</p>
</blockquote>
<h2 id="remove_method-undef_method">remove_method和undef_method</h2>
<p> 想要移除已存在的方法,你可以在一个打开的类的<strong>作用域(Scope)</strong>内使用<code>remove_method</code>方法。即使是父类以及父类的父类等先祖中有同名的方法,那些方法也不会被移除。而相比之下,<code>undef_method</code>会阻止任何对指定方法的访问,无论该方法是在对象所属的类中被定义,还是其父类及其先祖类。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>method_missing(m, *args, &block)
puts <span class="hljs-string">"Method Missing: Called <span class="hljs-subst">#{m}</span> with <span class="hljs-subst">#{args.inspect}</span> and <span class="hljs-subst">#{block}</span>"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>hello
puts <span class="hljs-string">"Hello from class Rubyist"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndianRubyist</span> <span class="hljs-inheritance">< <span class="hljs-parent">Rubyist</span></span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>hello
puts <span class="hljs-string">"Hello from class IndianRubyist"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">IndianRubyist</span>.new
obj.hello <span class="hljs-comment"># => Hello from class IndianRubyist</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndianRubyist</span></span>
remove_method <span class="hljs-symbol">:hello</span> <span class="hljs-comment"># removed from IndianRubyist, but still in Rubyist</span>
<span class="hljs-keyword">end</span>
obj.hello <span class="hljs-comment"># => Hello from class Rubyist</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndianRubyist</span></span>
undef_method <span class="hljs-symbol">:hello</span> <span class="hljs-comment"># prevent any calls to 'hello'</span>
<span class="hljs-keyword">end</span>
obj.hello <span class="hljs-comment"># => Method Missing: Called hello with [] and</span>
</code></pre>
<h2 id="eval">eval</h2>
<p> <code>Kernel</code>模块提供了一个叫做<code>eval</code>的方法,该方法用于执行一个用字符串表示的代码。<code>eval</code>方法可以计算多行代码,使得将整个程序代码嵌入到字符串中并执行成为了可能。<code>eval</code>方法很慢,在执行字符串前最好对其预先求值。不过,糟糕的是,<code>eval</code>方法会变得十分危险。如果外部数据通过<code>eval</code>传递的话,你就可能会遭遇一个安全漏洞,因为这些数据可能含有任意的代码,并且你的程序将会执行它。现在,<code>eval</code>方法是在万般无奈的情况下才被选择的。</p>
<pre><code class="lang-ruby">str = <span class="hljs-string">"Hello"</span>
puts eval(<span class="hljs-string">"str + ' Rubyist'"</span>) <span class="hljs-comment"># => "Hello Rubyist"</span>
</code></pre>
<p> 关于<code>eval</code>方法,苏小脉给出了下面的建议:</p>
<blockquote>
<p> 一般来说,能避免<code>eval</code>就尽量避免,因为<code>eval</code> 有额外的“分析时”开销(将字符串作为源代码进行词法、文法分析),而这个“剖析时”却又是在程序“运行时”进行的。把不需要惰性求值的表达式预先进行及早求值,能避免一些分析时开销。如果可能的话,用<code>instance_exec</code>,或<code>instance_eval</code> 带块的形式,也比直接在字符串上求值好。</p>
<p>——苏小脉在<a href="http://rm.66rpg.com/thread-165322-1-1.html" target="_blank">如果用这种方式来构造一些复杂的对象呢?</a>上的发言</p>
</blockquote>
<p> 而关于<code>eval</code>方法的安全性漏洞,Dave Thomas在他的著作Programming Ruby(<a href="http://www.rubycentral.com/pickaxe/taint.html" target="_blank">英文页面</a>)中列举了一个十分有趣的例子:</p>
<blockquote>
<p> Walter Webcoder有一个非常棒的想法:设计一个Web算数页面。该页面是含有一个文本域以及按钮的简单Web表单,并被各种各样的非常酷的数学链接和横幅广告包围,使得看起来丰富多彩。用户输入一个算术表达式到文本域中,并按下按钮,然后结果就会被显示出来。一夜之间,世界上所有计算器都变得无用了;Walter大大获利,然后他退休并把他的余生用于收集车牌号。</p>
<p>Walter认为实现这样一个计算器很容易。他可以用Ruby的<code>CGI</code>库访问表单域中的内容,再用<code>eval</code>方法把字符串当做表达式来求值。</p>
<pre><code class="lang-ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'cgi'</span>
cgi = <span class="hljs-constant">CGI::</span>new(<span class="hljs-string">"html4"</span>)
<span class="hljs-comment"># Fetch the value of the form field "expression"</span>
expr = cgi[<span class="hljs-string">"expression"</span>].to_s
<span class="hljs-keyword">begin</span>
result = eval(expr)
<span class="hljs-keyword">rescue</span> <span class="hljs-constant">Exception</span> => detail
<span class="hljs-comment"># handle bad expressions</span>
<span class="hljs-keyword">end</span>
<span class="hljs-comment"># display result back to user...</span>
</code></pre>
<p>Walter把这个应用程序放到网上才几秒钟,来自Waxahachie的一个12岁小孩在表单中输入了<code>system("rm")</code>,随他的计算机上的文件一起,Walter的美梦一下子破灭了。</p>
<p>Walter得到了一个重要的教训:所有的外部数据都是有危险的。不要让它们靠近那些可能改动你的系统的接口。在这个案例中,表单中的内容是外部数据,而对<code>eval</code>的调用正是一个安全漏洞。</p>
<p>——Programming Ruby 中文第二版,Dave Thomas,Chad Fowler,Andy Hunt著</p>
</blockquote>
<h2 id="instance_eval-module_eval-class_eval">instance_eval, module_eval, class_eval</h2>
<p> <code>instance_eval</code>,<code>module_eval</code>和<code>class_eval</code>是<code>eval</code>方法的特殊形式。</p>
<h3 id="instance_eval">instance_eval</h3>
<p> <code>Object</code>类提供了一个名为<code>instance_eval</code>的公开方法,该方法可被一个实例调用。他提供了操作对象的实例变量的途径。可以使用字符串向此方法传递参数或者传递一个代码块。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>initialize
<span class="hljs-variable">@geek</span> = <span class="hljs-string">"Matz"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new
<span class="hljs-comment"># instance_eval可以操纵obj的私有方法以及实例变量</span>
obj.instance_eval <span class="hljs-keyword">do</span>
puts <span class="hljs-keyword">self</span> <span class="hljs-comment"># => #<Rubyist:0x2ef83d0></span>
puts <span class="hljs-variable">@geek</span> <span class="hljs-comment"># => Matz</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p> 通过<code>instance_eval</code>传递的代码块使得你可以在对象内部操作。你可以在对象内部肆意操纵,不再会有任何数据是私有的!<code>instance_eval</code>亦可用于添加类方法。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-keyword">end</span>
<span class="hljs-constant">Rubyist</span>.instance_eval <span class="hljs-keyword">do</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>who
<span class="hljs-string">"Geek"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
puts <span class="hljs-constant">Rubyist</span>.who <span class="hljs-comment"># => Geek</span>
</code></pre>
<p> 还记得我们在之前匿名类的讲述(代码清单第四条)么?这个例子在这里被再一次的使用。</p>
<h3 id="module_eval-class_eval">module_eval, class_eval</h3>
<p> <code>module_eval</code>和<code>class_eval</code>方法用于模块和类,而不是对象。<code>class_eval</code>是<code>module_eval</code>的一个别名。<code>module_eval</code>和<code>class_eval</code>可用于从外部检索类变量。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-variable">@@geek</span> = <span class="hljs-string">"Ruby's Matz"</span>
<span class="hljs-keyword">end</span>
puts <span class="hljs-constant">Rubyist</span>.class_eval(<span class="hljs-string">"@@geek"</span>) <span class="hljs-comment"># => Ruby's Matz</span>
</code></pre>
<p> <code>module_eval</code>和<code>class_eval</code>方法亦可用于添加类或模块的实例方法。尽管名字上两个方法时不同的,但他们的功能是相同的,并且都可以在模块或者类上使用。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-keyword">end</span>
<span class="hljs-constant">Rubyist</span>.class_eval <span class="hljs-keyword">do</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>who
<span class="hljs-string">"Geek"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new
puts obj.who <span class="hljs-comment"># => Geek</span>
</code></pre>
<blockquote>
<p>备注</p>
<p>当作用于类时,<code>class_eval</code>将会定义实例方法,而<code>instance_eval</code>定义类方法。</p>
</blockquote>
<h2 id="class_variable_get-class_variable_set">class_variable_get, class_variable_set</h2>
<p> 添加或查询一个类变量,<code>class_variable_get</code>方法和<code>class_variable_set</code>方法都可以被使用。<code>class_variable_get</code>方法需要一个代表变量名称的符号作为参数,并返回变量的值。<code>class_variable_set</code>方法也需要一个代表变量名称的符号作为参数,同时也要求传递一个值,作为欲设定的值。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-variable">@@geek</span> = <span class="hljs-string">"Ruby's Matz"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-constant">Rubyist</span>.class_variable_set(<span class="hljs-symbol">:</span><span class="hljs-variable">@@geek</span>, <span class="hljs-string">'Matz rocks!'</span>)
puts <span class="hljs-constant">Rubyist</span>.class_variable_get(<span class="hljs-symbol">:</span><span class="hljs-variable">@@geek</span>) <span class="hljs-comment"># => Matz rocks!</span>
</code></pre>
<h2 id="class_variables">class_variables</h2>
<p> 如果你想知道一个类中有哪些类变量,我们可以使用<code>class_varibles方法</code>。他返回一个<strong>数组(Array)</strong>,以<strong>符号(Symbol)</strong>的形式返回类变量的名称。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-variable">@@geek</span> = <span class="hljs-string">"Ruby's Matz"</span>
<span class="hljs-variable">@@country</span> = <span class="hljs-string">"USA"</span>
<span class="hljs-keyword">end</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Child</span> <span class="hljs-inheritance">< <span class="hljs-parent">Rubyist</span></span></span>
<span class="hljs-variable">@@city</span> = <span class="hljs-string">"Nashville"</span>
<span class="hljs-keyword">end</span>
print <span class="hljs-constant">Rubyist</span>.class_variables <span class="hljs-comment"># => [:@<span class="hljs-yardoctag">@geek</span>, :@<span class="hljs-yardoctag">@country</span>]</span>
puts
p <span class="hljs-constant">Child</span>.class_variables <span class="hljs-comment"># => [:@<span class="hljs-yardoctag">@city</span>]</span>
</code></pre>
<p> 你可以从程序的输出中观察到<code>Child.class_variables</code>输出的是在<code>Child</code>类中定义的类变量(<code>@@city</code>)。<code>Child</code>类没有从父类中继承类变量(<code>@@geek</code>, <code>@@country</code>)。</p>
<h2 id="instance_variable_get-instance_variable_set">instance_variable_get, instance_variable_set</h2>
<p> 我们可以使用<code>instance_variable_get</code>方法查询实例变量的值。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>initialize(p1, p2)
<span class="hljs-variable">@geek</span>, <span class="hljs-variable">@country</span> = p1, p2
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new(<span class="hljs-string">'Matz'</span>, <span class="hljs-string">'USA'</span>)
puts obj.instance_variable_get(<span class="hljs-symbol">:</span><span class="hljs-variable">@geek</span>) <span class="hljs-comment"># => Matz</span>
puts obj.instance_variable_get(<span class="hljs-symbol">:</span><span class="hljs-variable">@country</span>) <span class="hljs-comment"># => USA</span>
</code></pre>
<p> 类比于<code>class_variable_set</code>,你可以使用<code>instance_variable_set</code>来设置一个对象的实例变量的值。</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rubyist</span></span>
<span class="hljs-function"><span class="hljs-keyword">def</span> </span>initialize(p1, p2)
<span class="hljs-variable">@geek</span>, <span class="hljs-variable">@country</span> = p1, p2
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = <span class="hljs-constant">Rubyist</span>.new(<span class="hljs-string">'Matz'</span>, <span class="hljs-string">'USA'</span>)
puts obj.instance_variable_get(<span class="hljs-symbol">:</span><span class="hljs-variable">@geek</span>) <span class="hljs-comment"># => Matz</span>
puts obj.instance_variable_get(<span class="hljs-symbol">:</span><span class="hljs-variable">@country</span>) <span class="hljs-comment"># => USA</span>
obj.instance_variable_set(<span class="hljs-symbol">:</span><span class="hljs-variable">@country</span>, <span class="hljs-string">'Japan'</span>)
puts obj.inspect <span class="hljs-comment"># => #<Rubyist:0x2ef8038 <span class="hljs-yardoctag">@country</span>="Japan", <span class="hljs-yardoctag">@geek</span>="Matz"></span>
</code></pre>
<p> 这样做的好处就是,你不需要使用<code>attr_accessor</code>等方法为访问实例变量建立接口。</p>
<h2 id="const_get-const_set">const_get, const_set</h2>
<p> 类似的,<code>const_get</code>和<code>const_set</code>用于操作常量。<code>const_get</code>返回指定常量的值:</p>
<pre><code>puts Float.const_get(:MIN) # => 2.2250738585072e-308
</code></pre><p> <code>const_set</code>为指定的常量设置指定的值,并返回该对象。如果常量不存在,那么他会创建该常量,就是下面示范的那样:</p>
<pre><code>class Rubyist
end
puts Rubyist.const_set("PI", 22.0/7.0) # => 3.14285714285714
</code></pre><p> 因为<code>const_get</code>返回常量的值,因此,你可以使用此方法获得一个类的名字并为这个类添加一个新的实例化对象的方法。这样使得我们有能力在运行时创建类并实例化其实例。</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># Let us call our new class 'Rubyist'</span>
<span class="hljs-comment"># (we could have prompted the user for a class name)</span>
class_name = <span class="hljs-string">"rubyist"</span>.capitalize
<span class="hljs-constant">Object</span>.const_set(class_name, <span class="hljs-constant">Class</span>.new)
<span class="hljs-comment"># Let us create a method 'who'</span>
<span class="hljs-comment"># (we could have prompted the user for a method name)</span>
class_name = <span class="hljs-constant">Object</span>.const_get(class_name)
puts class_name <span class="hljs-comment"># => Rubyist</span>
class_name.class_eval <span class="hljs-keyword">do</span>
define_method <span class="hljs-symbol">:who</span> <span class="hljs-keyword">do</span> |my_arg|
my_arg
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
obj = class_name.new
puts obj.who(<span class="hljs-string">'Matz'</span>) <span class="hljs-comment"># => Matz</span>
</code></pre>
</section>
</div>
</div>
</div>
<a href="./chapter03.html" class="navigation navigation-prev " aria-label="Previous page: 方法的调用"><i class="fa fa-angle-left"></i></a>
<a href="./chapter05.html" class="navigation navigation-next " aria-label="Next page: 绑定"><i class="fa fa-angle-right"></i></a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/mode-javascript.js"></script>
<script src="gitbook/jsrepl/jsrepl.js" id="jsrepl-script"></script>
<script src="gitbook/app.js"></script>
<script src="gitbook/plugins/gitbook-plugin-mixpanel/plugin.js"></script>
<script src="http://cdn.mathjax.org/mathjax/2.0-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script src="gitbook/plugins/gitbook-plugin-mathjax/plugin.js"></script>
<script>
require(["gitbook"], function(gitbook) {
var config = {};
gitbook.start(config);
});
</script>
</body>
</html>