26.md 12.4 KB
Newer Older
W
init  
wizardforcel 已提交
1 2
## 17.模块和混入

W
wizardforcel 已提交
3
每当你想到模块时,便会想到一个盒子或其他东西。 只需看看你的打印机即可。 它是一个模块。 它可以做一些事情。 它有事要做。 以类似的方式,Ruby 中的模块可以包含 Ruby 代码来执行某些操作。 模块是将 Ruby 代码打包到可能的逻辑单元中的方法。 每当你想在模块中使用代码时,只需将其包含在 Ruby 程序中即可。
W
init  
wizardforcel 已提交
4 5 6

让我们看一下第一个名为 [module_function.rb](code/module_function.rb) 的模块程序。 以下程序具有两个模块,即`Star``Dollar`。 这两个模块具有称为`line`的相同功能。 请注意,在模块`Star`中的函数`line`中,我们打印一行 20 个星号(*)字符。 在模块`Dollar`中的功能`line`中,我们以类似的方式打印了一行 20 美元($)的字符。 在文本编辑器中键入程序并执行。

W
wizardforcel 已提交
7
```rb
W
init  
wizardforcel 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# module_function.rb

module Star
  def line
    puts '*' * 20
  end
end

module Dollar
  def line
    puts '$' * 20
  end
end

include Star
line
include Dollar
line
```

输出量

W
wizardforcel 已提交
30
```rb
W
init  
wizardforcel 已提交
31 32 33 34 35 36
********************
$$$$$$$$$$$$$$$$$$$$
```

让我们看一下模块外部的程序。 我们有以下代码:

W
wizardforcel 已提交
37
```rb
W
init  
wizardforcel 已提交
38 39 40 41 42 43 44 45 46 47
include Star
line
include Dollar
line
```

`include Star`行中,我们包含了`Star`模块中的代码,然后我们调用了函数`line`,因此输出结果是一行 20 星。 看下一行,我们包含模块`Dollar``Dollar`也具有称为`line`的功能。 由于此模块是在`Star`之后调用的,因此 Dollar 模块中的`line`函数会被重写或正确地隐藏`Star`模块中的`line`函数。 因此,在`include Dollar`之后调用`line`将在`Dollar`模块的`line`功能中执行代码。 因此,我们得到一行二十美元的符号。

在下面的示例 [module_function_0.rb](code/module_function_0.rb) 中,我们将看到调用`line`函数而不包含任何模块时发生的情况。 在下面输入程序并执行

W
wizardforcel 已提交
48
```rb
W
init  
wizardforcel 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
# module_function_0.rb

module Star
  def line
    puts '*' * 20
  end
end

module Dollar
  def line
    puts '$' * 20
  end
end

line
```

Output

W
wizardforcel 已提交
68
```rb
W
init  
wizardforcel 已提交
69 70 71
module_function_0.rb:15:in `<main>': undefined local variable or method `line' for main:Object (NameError)
```

W
wizardforcel 已提交
72
如你所见,`line`被视为未定义的局部变量或方法 &lt;sup class="footnote"&gt;[ [39](#_footnotedef_39 "View footnote.") ]&lt;/sup&gt; 。 因此,可以说只有当模块包含在程序中时,才能访问模块中的功能。
W
init  
wizardforcel 已提交
73 74 75

可以说,我们编写了另一个模块,该模块不带任何功能,而只是其中的代码。 我写了以下程序 linclude:code / module.rb [module.rb]只是因为我想看看会发生什么。 事实证明,当模块在 Ruby 文件中编码并执行时,默认情况下将执行模块中的代码。 即使我们不包含该模块,也会发生这种情况。

W
wizardforcel 已提交
76
```rb
W
init  
wizardforcel 已提交
77 78 79 80 81 82 83 84 85 86 87 88 89
# module.rb

module Something
  puts "Something"
end

module Nothing
  puts "Nothing"
end
```

Output

W
wizardforcel 已提交
90
```rb
W
init  
wizardforcel 已提交
91 92 93 94 95 96 97 98 99 100 101 102
Something
Nothing
```

上面程序 [module.rb](code/module.rb) 的输出打印出`Something``Nothing`。 我们放置了两个名为`Something`的模块,其中包含代码`puts “Something”`,另一个包含了`Nothing`的模块,其中包含`puts “Nothing”`。 尽管我没有使用`include`语句将这些模块包含在程序中,但无论如何它们下的代码仍会执行。

### 17.1。 不包含的调用函数

在程序 [module_function.rb](code/module_function.rb) 中,我们已经看到了如何包含模块并在其中调用函数。 我们印了一行星星和美元。 让我们以不同的方式做同样的事情。 这次,我们将不再使用 include 关键字。

键入程序 [module_function_1.rb](code/module_function_1.rb) 并执行它。

W
wizardforcel 已提交
103
```rb
W
init  
wizardforcel 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
# module_function_1.rb

module Star
  def Star.line
    puts '*' * 20
  end
end

module Dollar
  def Dollar.line
    puts '$' * 20
  end
end

Dollar::line
Star::line
Dollar::line
```

Output

W
wizardforcel 已提交
125
```rb
W
init  
wizardforcel 已提交
126 127 128 129 130 131 132
$$$$$$$$$$$$$$$$$$$$
********************
$$$$$$$$$$$$$$$$$$$$
```

看下面的代码:

W
wizardforcel 已提交
133
```rb
W
init  
wizardforcel 已提交
134 135 136 137 138
Dollar::line
Star::line
Dollar::line
```

W
wizardforcel 已提交
139
当我们调用`Dollar::line`时,将执行`Dollar`模块中的`line`函数。 当我们调用`Star::line`时,将执行`Star`模块中的`line`函数。 因此,当你想在模块中调用函数时,请使用以下语法`&lt;module-name&gt;::&lt;function-name&gt;`
W
init  
wizardforcel 已提交
140 141 142 143 144

请注意,在模块`Star`中,我们将功能线定义为`Star.line`,而不仅仅是`line`。 同样,在模块`Dollar`中,我们将其定义为`Dollar.line`

好,我们开始了解模块,现在让我们开始动手。 键入下面的代码( [module_function_2.rb](code/module_function_2.rb) )并执行它。

W
wizardforcel 已提交
145
```rb
W
init  
wizardforcel 已提交
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
# module_function_2.rb

module Star
  def Star.line
    puts '*' * 20
  end
end

module Dollar
  def Dollar.line
    puts '$' * 20
  end
end

module At
  def line
    puts '@' * 20
  end
end

include At

Dollar::line
Star::line
Dollar::line
line
```

Output

W
wizardforcel 已提交
176
```rb
W
init  
wizardforcel 已提交
177 178 179 180 181 182
$$$$$$$$$$$$$$$$$$$$
********************
$$$$$$$$$$$$$$$$$$$$
@@@@@@@@@@@@@@@@@@@@
```

W
wizardforcel 已提交
183
好的,你有一些输出。 看一下以下几行
W
init  
wizardforcel 已提交
184

W
wizardforcel 已提交
185
```rb
W
init  
wizardforcel 已提交
186 187 188 189 190 191 192 193 194 195 196 197
include At

Dollar::line
Star::line
Dollar::line
line
```

请注意,我们首先使用`include At`语句包含了`At`模块。 在执行`Dollar::line`语句时,我们得到了一行二十美元的输出。 在执行`Star::line`时,我们得到 20 星的输出。 接下来,我们再次调用`Dollar::line`,然后抓住问题。 我们只调用函数`line`。 由于我们首先包含了`At`,因此当遇到`line`语句时,它将调用`At`模块中的 line 方法。 这表明尽管我们已经调用了`Dollar::line``Star::line`,但它并未在程序中包含 &lt;sup class="footnote"&gt;[ [40](#_footnotedef_40 "View footnote.") ]&lt;/sup&gt; 模块代码,而是仅执行了程序中的特定功能 模块。

在 link:code / module_function _1.rb [module_function _1.rb]中,我们看到了如何在模块中调用函数`Star::line`,其中`Star`是模块名称,`line`是函数名称。 为此,我们在`Star`模块中定义了`line`函数,如下所示

W
wizardforcel 已提交
198
```rb
W
init  
wizardforcel 已提交
199 200 201 202 203 204 205
def Star.line
        puts '*' * 20
end
```

我们将其命名为`Star.line`而不是仅将其命名为`line`。 请注意, [module_function_3.rb](code/module_function_3.rb)[module_function_1.rb](code/module_function_1.rb) 相似,但对`Dollar`模块中的`line`功能进行了深入研究。 它没有命名为`Dollar.line`,而是命名为`Star.line`。 如果我们将这样的代码弄乱了怎么办? 执行以下程序并查看。

W
wizardforcel 已提交
206
```rb
W
init  
wizardforcel 已提交
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
# module_function_3.rb

module Star
  def Star.line
    puts '*' * 20
  end
end

module Dollar
  def Star.line
    puts '$' * 20
  end
end

module At
  def line
    puts '@' * 20
  end
end

include At

Dollar::line
Star::line
Dollar::line
line
```

Output

W
wizardforcel 已提交
237
```rb
W
init  
wizardforcel 已提交
238 239 240 241 242 243 244 245 246 247
@@@@@@@@@@@@@@@@@@@@
$$$$$$$$$$$$$$$$$$$$
@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@
```

注意,每当我们调用`Dollar::line`时,都会调用`At`模块中的`line`函数。 这是因为由于我们在`Dollar`模块中将其定义为`Star.line`,所以`Dollar::line`不存在,因此在`At`模块中调用了功能`line`。 注意,我们已经使用语句 include `At`包含了`At`模块。

现在我们考虑另一种情况,在`Dollar`模块中(参见程序 [module_function_4.rb](code/module_function_4.rb) ),我们仅将函数`line`定义为`line`而不是`Dollar.line`。 在下面的程序中使用`Dollar::line`调用它时,我们看到`At`模块中的`line`函数被调用。 因此,故事的寓意是,如果要在程序中调用`&lt;module-name&gt;::&lt;function-name&gt;`,请确保该函数在模块内的名称为`&lt;module-name&gt;.&lt;function-name&gt;`

W
wizardforcel 已提交
248
```rb
W
init  
wizardforcel 已提交
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
# module_function_4.rb

module Star
  def Star.line
    puts '*' * 20
  end
end

module Dollar
  def line
    puts '$' * 20
  end
end

module At
  def line
    puts '@' * 20
  end
end

include At

Dollar::line
Star::line
Dollar::line
line
```

Output

W
wizardforcel 已提交
279
```rb
W
init  
wizardforcel 已提交
280 281 282 283 284 285 286 287 288 289
@@@@@@@@@@@@@@@@@@@@
********************
@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@
```

### 17.2。 模块中的类

我们已经了解了 Ruby 解释器如何对模块中的函数进行操作。 现在让我们看一下它对模块中类的行为。 在 [module_class.rb](code/module_class.rb) 下键入程序并执行。

W
wizardforcel 已提交
290
```rb
W
init  
wizardforcel 已提交
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
# module_class.rb

module Instrument
  class Ruler
    def what_u_do?
      puts "I take measurements"
    end
  end
end

module People
  class Ruler
    def what_u_do?
      puts "I govern this land"
    end
  end
end

r1 = People::Ruler.new
r2 = Instrument::Ruler.new

r1.what_u_do?
r2.what_u_do?
```

Output

W
wizardforcel 已提交
318
```rb
W
init  
wizardforcel 已提交
319 320 321 322 323 324 325 326
I govern this land
I take measurements
```

让我们分析程序。 我们有两个模块。 第一个名为`Instrument`,第二个名为`People`。 两者都有一个名为`Ruler`的类,并且两个 Ruler 都有一个名为`what_u_do?`的方法。 很好,现在进入程序。 我们有一条语句`r1 = People::Ruler.new`,其中`r1`成为`People`模块中`Ruler`类的实例变量。 同样,我们具有`r2 = Instrument::Ruler.new`,其中`r2`成为`Instrument`模块中`Ruler`类的实例变量。

可以通过执行以下代码来验证

W
wizardforcel 已提交
327
```rb
W
init  
wizardforcel 已提交
328 329 330 331 332 333
r1.what_u_do?
r2.what_u_do?
```

其中调用`r1.what_u_do?``输出`I govern this land`,调用`r2.what_u_do?`输出`I take measurements`。

W
wizardforcel 已提交
334
这个故事的寓意是,你可以在不同的模块中使用相同的类名称,只需使用`&lt;module-name&gt;::&lt;class-name&gt;`即可调用该类。 。
W
init  
wizardforcel 已提交
335 336 337

### 17.3。 混合蛋白

W
wizardforcel 已提交
338
模块的另一种用途是你可以根据需要在模块中混合代码。 这个叫做 mixin。 我们已经了解了 mixin,但我没有告诉你这是 mixin。 例如,假设你正在用 Ruby for Linux 和 Apple 机器编写某些应用程序。 你会发现某些代码仅在 Linux 上有效,而其他代码仅在 Apple 上有效,然后可以如下所示将它们分开。 Apple 组件进入 Apple 模块,Linux 组件进入 Linux 模块。
W
init  
wizardforcel 已提交
339

W
wizardforcel 已提交
340
假设你的朋友使用 Linux 机器并想要运行你的程序。 你需要做的就是在代码中包含 Linux,如下所示。
W
init  
wizardforcel 已提交
341

W
wizardforcel 已提交
342
```rb
W
init  
wizardforcel 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
# mixin.rb

module Linux
  # Code for linux goes here
  def function
    puts "This function contains code for Linux systems"
  end
end

module Apple
  # Code for apple goes here
  def function
    puts "This function contains code for Apple systems"
  end
end

include Linux

function
```

Output

W
wizardforcel 已提交
366
```rb
W
init  
wizardforcel 已提交
367 368 369
This function contains code for Linux systems
```

W
wizardforcel 已提交
370
调用方法`function`时,将调用`Linux`模块中的方法`function`。 简而言之,你已经在程序中混入了 Linux 代码,并保留了 Apple 的东西。
W
init  
wizardforcel 已提交
371

W
wizardforcel 已提交
372
让我们看看 mixin 的另一个例子。 看看下面的代码 [mixin_2.rb](code/mixin_2.rb) 。 可以说你的客户告诉他,他非常需要一个程序来计算圆的面积和球的体积。 因此,你开发了两个名为`Circle`和`Sphere`的类,并配有代码来查找面积和体积。 所以你的客户很高兴。 由于你的客户位于银河星系中,因此常数 Pi &lt;sup class="footnote"&gt;[ [41](#_footnotedef_41 "View footnote.") ]&lt;/sup&gt; 为 22 除以 7。因此,我们将`Pi`的值放在名为`Constants`的模块中,然后 使用语句`include Constants`包含在`Circle`和`Sphere`类中。 在下面的程序中键入并执行它。
W
init  
wizardforcel 已提交
373

W
wizardforcel 已提交
374
```rb
W
init  
wizardforcel 已提交
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
# mixin_2.rb

module Constants
  Pi = 22.0/7
end

class Circle
  include Constants
  attr_accessor :radius

  def area
    Pi * radius * radius
  end
end

class Sphere
  include Constants
  attr_accessor :radius

  def volume
    (4.0/3) * Pi * radius ** 3
  end
end

c = Circle.new
c.radius = 7
s = Sphere.new
s.radius = 7
puts "Circle Area = #{c.area}"
puts "Sphere Volume = #{s.volume}"
```

Output

W
wizardforcel 已提交
409
```rb
W
init  
wizardforcel 已提交
410 411 412 413
Circle Area = 154.0
Sphere Volume = 1437.333333333333
```

W
wizardforcel 已提交
414
这样你就可以得到一些输出。 你可能会问,将常量放入模块并使用`include`语句将其混合在类中有何好处? 上面的程序教你两种道德
W
init  
wizardforcel 已提交
415

W
wizardforcel 已提交
416
*   你可以将常量放入模块中
W
init  
wizardforcel 已提交
417

W
wizardforcel 已提交
418
*   如果你具有可以在类之间共享的通用代码 &lt;sup class="footnote"&gt;[ [42](#_footnotedef_42 "View footnote.") ]&lt;/sup&gt; ,则可以将其放入模块中并进行共享。
W
init  
wizardforcel 已提交
419

W
wizardforcel 已提交
420
如果你在每个类中分别定义了`Pi`的值,并且你碰巧从仙女座星系中获得了 Pi 为 57 除以 18.1364 的客户端,则可以只在一个地方进行更改,即在`Constants`模块和 看到更改反映在许多类中(在我们的例子中是`Circle`和`Sphere`类)。
W
init  
wizardforcel 已提交
421 422

因此,道德模块有助于我们迎合超出我们银河系的客户,并且我们可以真正建立银河帝国 &lt;sup class="footnote"&gt;[ [43](#_footnotedef_43 "View footnote.") ]&lt;/sup&gt;