20.md 45.1 KB
Newer Older
W
init  
wizardforcel 已提交
1 2 3 4 5 6
## 11.类&对象

### 11.1。 创建一个正方形

可以将类视为一个名称捆绑在一起的变量和函数。 为了说明类,让我们看一个不起眼的对象,叫做 Square。 正方形是一个非常简单的几何形状,具有四个等长的边,如何在 Ruby 程序中表示这个正方形? 现在,我们编写一个称为 Square 的空类,如下所示:

W
wizardforcel 已提交
7
```rb
W
init  
wizardforcel 已提交
8 9 10 11 12 13 14 15 16 17
#square.rb

class Square
end
```

我们写了一个空课。 `class`一词表明我们正在编写一个类的定义。 class 关键字之后是类的名称,在本例中为`Square`。 必须注意,Ruby 中的类名称必须以大写字母 <sup class="footnote">[ [28](#_footnotedef_28 "View footnote.") ]</sup> 开头。

一个正方形有四个边,都长相同。 现在,我们将一个名为`side_length`的变量放入正方形。

W
wizardforcel 已提交
18
```rb
W
init  
wizardforcel 已提交
19 20 21 22 23 24 25
#square.rb

class Square
  attr_accessor :side_length
end
```

W
wizardforcel 已提交
26
你可能会问`attr_accessor`是什么? 它代表`attribute accessor`,使你可以轻松获取和设置`side_length`。 让我们使用这个正方形类,看看它是如何工作的。 修改上面的代码,如下所示:
W
init  
wizardforcel 已提交
27

W
wizardforcel 已提交
28
```rb
W
init  
wizardforcel 已提交
29 30 31 32 33 34 35 36 37 38 39
#square.rb

class Square
  attr_accessor :side_length
end

s1 = Square.new # creates a new square
s1.side_length = 5 # sets its side length
puts "Side length of s1 = #{s1.side_length}" # prints the side length
```

W
wizardforcel 已提交
40
当你执行以上代码时,这将是你得到的结果
W
init  
wizardforcel 已提交
41

W
wizardforcel 已提交
42
```rb
W
init  
wizardforcel 已提交
43 44 45 46 47
Side length of s1 = 5
```

让我们遍历新添加的代码,走线

W
wizardforcel 已提交
48
```rb
W
init  
wizardforcel 已提交
49 50 51 52 53
s1 = Square.new
```

在上面的语句中,我们创建一个新的 Square 并将其参数存储到名为`s1`的变量中。 可以使用`<class name>.new`创建一个新的类实例。 创建一个新的正方形后,我们现在可以使用点运算符“。”访问其`side_length`。 因此,我们首先使用以下语句将`side_length`设置为五个单位

W
wizardforcel 已提交
54
```rb
W
init  
wizardforcel 已提交
55 56 57 58 59
s1.side_length = 5
```

现在已经分配了`side_length`,我们可以将其用于任何目的。 现在,我们只需使用以下语句即可打印正方形的`side_length`

W
wizardforcel 已提交
60
```rb
W
init  
wizardforcel 已提交
61 62 63 64 65 66 67
puts "Side length of s1 = #{s1.side_length}"
```

### 11.2。 班级职能

我们有一个名为`Square`的类,它具有一个名为`side_length`的属性。 使用`side_length`,我们可以找到正方形区域,其周长和对角线长度。 在此示例中,我将查找面积和周长。 因此,让我们添加两个函数来查找`area``perimeter`。 如下所示修改代码(我将修改后的代码保存在名为 [square_1.rb](code/square_1.rb) 的文件中)

W
wizardforcel 已提交
68
```rb
W
init  
wizardforcel 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
#square_1.rb

class Square
  attr_accessor :side_length

  def area
    @side_length * @side_length
  end

  def perimeter
    4 * @side_length
  end
end
```

W
wizardforcel 已提交
84
在上面的代码中,你看到我添加了两个函数,一个名为`area`,另一个名为`perimeter`,分别计算并返回正方形的面积和周长。 这些函数与我们之前创建的任何其他函数非常相似,只是现在将其放置在类中。 让我们编写一些其他代码来利用我们添加的新功能,只需修改程序,使其看起来像下面的代码
W
init  
wizardforcel 已提交
85

W
wizardforcel 已提交
86
```rb
W
init  
wizardforcel 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
#square_1.rb

class Square
  attr_accessor :side_length

  def area
    @side_length * @side_length
  end

  def perimeter
    4 * @side_length
  end
end

a = Square.new
a.side_length = 5
puts "Area: #{a.area}"
puts "Perimeter: #{a.perimeter}"
```

W
wizardforcel 已提交
107
运行示例,这是你将得到的输出
W
init  
wizardforcel 已提交
108

W
wizardforcel 已提交
109
```rb
W
init  
wizardforcel 已提交
110 111 112 113 114 115
Area: 25
Perimeter: 20
```

解释很简单。 在以下几行

W
wizardforcel 已提交
116
```rb
W
init  
wizardforcel 已提交
117 118 119 120 121 122
a = Square.new
a.side_length = 5
```

我们声明了一个新的`Square`,并已将`side_length`分配为 5 个单位。 在下面的几行中,我们只需打印出`a.area``a.perimeter`的值

W
wizardforcel 已提交
123
```rb
W
init  
wizardforcel 已提交
124 125 126 127
puts "Area: #{a.area}"
puts "Perimeter: #{a.perimeter}"
```

W
wizardforcel 已提交
128
了解我们如何嵌入“ a”的面积和周长的值(在上面的代码中)。 如果你正在阅读这本书,那么对你来说必须是新的一件事如下所示:
W
init  
wizardforcel 已提交
129

W
wizardforcel 已提交
130
```rb
W
init  
wizardforcel 已提交
131 132 133 134 135 136 137 138 139
def area
  @side_length * @side_length
end
```

我们知道 square 具有一个名为`side_length`的属性,该属性由语句`attr_accessor :side_length`定义,正如上面突出显示的代码所示,我们使用了`@side_length`而不是`side_length`,这是因为在类内部,类变量是 带有`@`(at)符号的前缀。 这有助于我们在类变量和具有相同名称的局部变量或函数之间进行标识。

### 11.3。 初始化程序或构造函数

W
wizardforcel 已提交
140
在前面处理正方形的示例中,你是否想过当你说`s = Square.new`时会发生什么? 好吧,在这种情况下,将创建一个新的`Square`并将其放入变量中。 如果有人问天气问题,当 Square 初始化时我们可以做什么? 答案是肯定的。 你所要做的就是将代码放入称为`initialize`的函数中,只要有`<class name>.new call`,就会调用此函数
W
init  
wizardforcel 已提交
141 142 143

看一下示例 [square2.rb](code/square2.rb) ,仔细看下面的代码,我们定义了一个名为`initialize`的函数,该函数接受一个名为`side_length`的参数,其默认值为零。 如果指定了`side_length`,则它将平方类中的`@side_length`属性设置为该值,否则`@side_length`的默认值为零。 在文本编辑器中输入 [square2.rb](code/square2.rb) 并执行

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

class Square
  attr_accessor :side_length

  def initialize side_length = 0
    @side_length = side_length
  end

  def area
    @side_length * @side_length
  end

  def perimeter
    4 * @side_length
  end
end

s1 = Square.new 4
s2 = Square.new
s2.side_length = 5

puts "Area of s1 is #{s1.area} squnits"
puts "Perimeter of s2 is #{s2.perimeter} units"
```

输出量

W
wizardforcel 已提交
173
```rb
W
init  
wizardforcel 已提交
174 175 176 177 178 179
Area of s1 is 16 squnits
Perimeter of s2 is 20 units
```

在程序中集中于以下几行

W
wizardforcel 已提交
180
```rb
W
init  
wizardforcel 已提交
181 182 183 184 185 186 187 188 189
s1 = Square.new 4
s2 = Square.new
s2.side_length = 5
```

在第一行`s1 = Square.new 4`中,我们创建一个名为`s1`的正方形,其`@side_length`为 4 个单位。 在第二行`s2 = Square.new`中,我们创建一个名为`s2`的 Square,最初将其边长(`@side_length`)设置为零个单位,仅在第三行`s2.side_length = 5`中将其`@side_length`设置为 5 个单位。

在其余的代码中

W
wizardforcel 已提交
190
```rb
W
init  
wizardforcel 已提交
191 192 193 194 195 196 197 198 199 200
puts "Area of s1 is #{s1.area} squnits"
puts "Peimeter of s2 is #{s2.perimeter} units"
```

我们打印`Square` `s1`的面积和`Square` `s2`的周长,以产生所需的输出。

### 11.4。 未公开的实例变量

不需要使用`attr_accessor`公开实例变量,它可以隐藏,可以从函数中设置和调用,如图所示

W
wizardforcel 已提交
201
```rb
W
init  
wizardforcel 已提交
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
# unexposed_class_variables.rb

class Human
  def set_name name
    @name = name
  end

  def get_name
    @name
  end
end

a = Human.new
a.set_name "Karthik"
b = Human.new
b.set_name "Chubby"

puts "#{a.get_name} and #{b.get_name} are best friends."
```

Output

W
wizardforcel 已提交
224
```rb
W
init  
wizardforcel 已提交
225 226 227
Karthik and Chubby are best friends.
```

W
wizardforcel 已提交
228
在上面的示例中,注意`Human`类的(实例)确实使用了名为`@name`的变量,但你可以将其设置为`a.name =`,而不能像`a.name`那样获取它,因为它没有暴露给 使用`attr_accessor`的外部世界。 因此,在 Ruby 类中,你可以在类中具有任意数量的隐藏变量,而无需外界对此有所了解。 那不漂亮吗?
W
init  
wizardforcel 已提交
229 230 231

### 11.5。 私人方法

W
wizardforcel 已提交
232
默认情况下,类中的方法或函数是公共的(可以在类范围之外访问),如果你不希望类外的程序访问它们,则可以将其设为私有。 让我们创建一个名为`Human`的类,并在其中放入一个私有方法,让我们尝试从该类外部访问它,然后看看会发生什么。 输入程序并执行
W
init  
wizardforcel 已提交
233

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

class Human
  attr_accessor :name, :age

  def tell_about_you
    puts "Hello I am #{@name}. I am #{@age} years old"
  end

  private

  def tell_a_secret
    puts "I am not a human, I am a computer program. He! Hee!!"
  end

end

h = Human.new
h.name = "Zigor"
h.age = 314567
h.tell_about_you
h.tell_a_secret # this wont work
```

上面的程序在执行时产生以下结果

W
wizardforcel 已提交
261
```rb
W
init  
wizardforcel 已提交
262 263 264 265 266 267
Hello I am Zigor. I am 314567 years old
human.rb:20: private method `tell_a_secret' called for #<Human:0xb7538678 @name="Zigor", @age=314567> (NoMethodError)
```

查看类中的函数`tell_a_secret`,将其放置在关键字`private`下,这将使其无法从类外部访问。 请注意,当我们调用方法`tell_a_secret`时,该行抛出一个错误,实际上,它说的是无方法错误(`NoMethodError`),这意味着被调用的方法在类中不存在。 这并不意味着计算机在撒谎,而是在安全地保密。

W
wizardforcel 已提交
268
在编程时,你只让程序的某些部分对其他人可见,这有助于使界面保持简单,并仅向用户提供他们真正需要编写代码的资源,这种隐藏不必要的东西使编程变得不复杂。
W
init  
wizardforcel 已提交
269

W
wizardforcel 已提交
270
有人可能会问,如果没有办法访问私有方法,那为什么我们需要它呢? 好了,你可以在下面的示例中间接访问它。 输入并执行
W
init  
wizardforcel 已提交
271

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

class Human
  attr_accessor :name, :age

  def tell_about_you
    puts "Hello I am #{@name}. I am #{@age} years old"
  end

  def confess
    tell_a_secret
  end

  private

  def tell_a_secret
    puts "I am not a human, I am a computer program. He! Hee!!"
  end

end

h = Human.new
h.name = "Zigor"
h.age = 314567
h.tell_about_you
h.confess
```

结果就是这样

W
wizardforcel 已提交
303
```rb
W
init  
wizardforcel 已提交
304 305 306 307 308 309 310 311 312 313 314 315 316 317
Hello I am Zigor. I am 314567 years old
I am not a human, I am a computer program. He! Hee!!
```

好好看看 confess 方法,在其中我们称为私有方法`tell_a_secret`,因此当我们甚至在类(`h.confess`)之外调用 confess 时,公共的`confess`方法也称为私有方法,因为`confess``Human`类中,它可以无障碍地访问`Human`中的任何私有方法,因此程序可以完美执行。

### 11.6。 类变量和方法

到现在为止,我们已经学会了创建一个类,我们知道一个类可以具有某些属性,例如人类可能具有诸如姓名,年龄等属性....我们知道该类可以包含一些可以被以下对象调用的功能 变量,它是类的实例。 现在,如果我们要调用一个函数或一个类而不声明一个变量即该类的实例怎么办? 可以在不声明属于类类型的实例变量的情况下调用的函数称为类方法。 那些无需使用类的实例变量即可访问的变量称为类变量。

让我们看一个演示类变量和方法的程序。 在下面的程序 [class_var_methods.rb](code/class_var_methods.rb) 中,我将创建一个名为`Robot`的类。 它将具有一个名为`@@robot_count`的类变量,该变量将跟踪创建的机器人数量。 由于它是一个类变量,我们通过在其前面使用两个@符号将其指示给计算机,因此在程序中,我们将其表示为`@@robot_count`。 我们创建了一个名为`robots_created`的函数,该函数将返回创建的机器人数量。 注意(在下面的程序中)函数`robots_created`编写为`self.robots_created`,关键字`self`告诉计算机可以在不声明实例对象的情况下调用此函数。

在文本编辑器中输入 class_var_method.rb 下面显示的程序并执行

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

class Robot
  def initialize
    if defined?(@@robot_count)
      @@robot_count += 1
    else
      @@robot_count = 1
    end
  end

  def self.robots_created
    @@robot_count
  end
end

r1 = Robot.new
r2 = Robot.new
puts "Created #{Robot.robots_created} robots"
r3, r4, r5 = Robot.new, Robot.new, Robot.new
puts "Created #{Robot.robots_created} robots"
```

执行以上程序时,将得到以下结果

W
wizardforcel 已提交
344
```rb
W
init  
wizardforcel 已提交
345 346 347 348 349 350
Created 2 robots
Created 5 robots
```

让我们看看`initialize`方法,并分析我们如何跟踪已创建的机器人数量。 在语句中创建第一个机器人时

W
wizardforcel 已提交
351
```rb
W
init  
wizardforcel 已提交
352 353 354 355 356
r1 = Robot.new
```

由于第一次没有定义变量`@@robot_count`,因此程序控制转到`initialize`方法,因此下面的`if`语句中的条件

W
wizardforcel 已提交
357
```rb
W
init  
wizardforcel 已提交
358 359 360 361 362 363 364 365 366 367 368
if defined?(@@robot_count)
        @@robot_count += 1
else
        @@robot_count = 1
end
```

失败,代码转到 else 部分,`@@robot_count = 1`定义了变量`@@robot_count`并初始化为值 1。

在第二条语句中,我们使用以下命令创建名为 r2 的机器人

W
wizardforcel 已提交
369
```rb
W
init  
wizardforcel 已提交
370 371 372 373 374 375 376
r2 = Robot.new
```

控件再次进入`initialize`方法,`if`语句通过,因为在创建`r1`时已经定义了`@@robot_count`,现在`@@robot_count`递增 1,现在变为 2。

接下来是我们称为`Robot.robots_created``puts`语句,该语句仅返回`@@robot_count`,因此将输出 2。 以下语句中的下一步:

W
wizardforcel 已提交
377
```rb
W
init  
wizardforcel 已提交
378 379 380 381 382 383 384
r3, r4, r5 = Robot.new, Robot.new, Robot.new
```

我们创建了三个新的机器人`r3``r4``r5`。 现在``@@robot_count`将增加到 5。在下一个`puts`语句中,将打印结果。 故事的寓意是这样,课堂方法有一个`self`。 (自点)之前,类变量前面有两个@(`@@`)。

很好,希望一切对读者都适用。 为什么现在我们对`robot_count`变量使用`attr_reader`,以便我们的程序变得简化,如下所示。 输入并执行。

W
wizardforcel 已提交
385
```rb
W
init  
wizardforcel 已提交
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
# attr_for_classvar.rb
# this program dosent work

class Robot
  attr_reader :robot_count

  def initialize
    if defined?(@@robot_count)
      @@robot_count += 1
    else
      @@robot_count = 1
    end
  end
end

r1 = Robot.new
r2 = Robot.new
puts "Created #{Robot.robot_count} robots"
r3, r4, r5 = Robot.new, Robot.new, Robot.new
puts "Created #{Robot.robot_count} robots"
```

你得到了什么结果? 可以将`attr_reader`用于类变量吗?

### 11.7。 遗产

我们从猴子进化而来。 黑猩猩看起来像我们,我们都有许多共同的特征,我们拥有许多与黑猩猩相似的属性,它们是如此相似,以至于黑猩猩在我们面前被送入太空,以了解零重力对猴子身体的影响。 只有当科学家感到安全时,他们才向人类发送 &lt;sup class="footnote"&gt;[ [29](#_footnotedef_29 "View footnote.") ]&lt;/sup&gt; 。 当人类从猴子进化而来时,他从猴子那里继承了很多东西,例如我们看起来像猴子,不相信吗? 只是去站在镜子前面!

W
wizardforcel 已提交
414
好的,在编程世界中,我们有一个称为继承的事物,其中一个类可以具有另一类的属性,而只需很少(或有时是极端的)变化。 让我告诉你一个数学真理,“正方形是所有边都相等的矩形”,不是吗? 所有正方形都是矩形,但并非所有矩形都是正方形。 我们将使用这些东西来编写我们的下一个程序 [Inheritance.rb](code/inheritance.rb) 。 在文本编辑器中编写程序并执行。
W
init  
wizardforcel 已提交
415

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

class Rectangle
  attr_accessor :length, :width

  def initialize length, width
    @length = length
    @width = width
  end

  def area
    @length * @width
  end

  def perimeter
    2 * (@length + @width)
  end
end

class Square < Rectangle

  def initialize length
    @width = @length = length
  end

  def side_length
    @width
  end

  def side_length=(length)
    @width = @length = length
  end
end

s = Square.new 5
puts "Perimeter of the square s is #{s.perimeter}"
r = Rectangle.new 3, 5
puts "Area of rectangle r is #{r.area}"
```

执行后,上面的程序将产生以下结果

W
wizardforcel 已提交
459
```rb
W
init  
wizardforcel 已提交
460 461 462 463 464 465
Perimeter of the square s is 20
Area of rectangle r is 15
```

仔细阅读程序,我们定义了一个名为`Rectangle`的类,它具有两个属性,即`@length`和`@width`。 在语句`r = Rectangle.new 3, 5`中初始化它时,我们传递了这两个参数。 当调用`area`时,将返回这两个属性的乘积,当使用某些天才公式调用其`perimeter`时,将计算并返回周长。 然后,我们定义一个名为`Square`的类,该类继承`Rectangle`的属性。 要说类`Square`继承了`Rectangle`,我们使用`&lt;`(小于)符号,如下所示

W
wizardforcel 已提交
466
```rb
W
init  
wizardforcel 已提交
467 468 469 470 471 472 473 474 475 476 477
class Square < Rectangle
```

看看`Square`类中的`initialize`方法,它仅使用一个参数`length`,它用于设置属性`@length`和`@width`的值。 由于`Square`继承了`Rectangle`类,因此默认情况下会获取`Rectangle`的所有属性和方法。 设置 Square 的`@width`和`@height`之后,我们现在可以调用 Square 的`area`和`perimeter`函数,就像我们对`Rectangle`所做的那样。

### 11.8。 覆盖方法

我们已经看到,类可以从其基类继承属性和方法。 假设我们有一个类`A`,它是`B`的父类(即 B 继承了 A),现在场景是`B`中定义了一个方法,该方法与`A`中的方法同名 ]。 当我们创建类型为`B`的实例变量并调用该方法`name`时,它将检查该方法是否在类`B`中存在,如果是,则将执行该方法,如果在`B`中未找到该方法, 然后 Ruby 解释器将在类`A`中对其进行检查,如果发现其已执行,则引发`NoMethodError` &lt;sup class="footnote"&gt;[ [30](#_footnotedef_30 "View footnote.") ]&lt;/sup&gt; 。

为了清楚起见,请看一个示例,键入并执行 [overlay_methods.rb](code/override_methods.rb)

W
wizardforcel 已提交
478
```rb
W
init  
wizardforcel 已提交
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
#!/usr/bin/ruby
# override_methods.rb

class A
  def belongs_to
    puts "I belong to class A"
  end

  def another_method
    puts "Just another method in class A"
  end
end

class B < A
  def another_method
    puts "Just another method in class B"
  end
end

a = A.new
b = B.new
a.belongs_to
a.another_method
b.belongs_to # This is not overriden so method in class A is called
b.another_method # This is overridden so method in class B is called
```

结果

W
wizardforcel 已提交
508
```rb
W
init  
wizardforcel 已提交
509 510 511 512 513 514 515 516
I belong to class A
Just another method in class A
I belong to class A
Just another method in class B
```

看一下结果。 调用`a.belongs_to`时,程序将按照在`A`类中定义的输出`I belong to class A`。 当调用`a.another_method`时,我们看到程序将打印出`Just another method in class A`,如在`A`类中定义的那样。 当`b.belongs_to is`调用该程序时,由于`B`类中没有`belongs_to`方法,因此再次打印出`I belong to class A`,因此将调用父方法。 参见调用`b.another_method`时的情况,程序会打印出`Just another method in class B`而不显示`Just another method in class A`,因为`B`的范围为`another_method`,因此无需在`A`类中查找该方法。

W
wizardforcel 已提交
517
我们将把覆盖概念更进一步,知道 Ruby 中的一切都是对象。 Ruby 纯粹是一种面向对象的编程语言。 拍摄你的 irb 并输入以下内容
W
init  
wizardforcel 已提交
518

W
wizardforcel 已提交
519
```rb
W
init  
wizardforcel 已提交
520 521 522 523 524 525 526 527 528 529
>> "Something".class
=> String
>> 1.class
=> Integer
>> 3.14278.class
=> Float
```

在 Ruby 中,只要在对象后放置`.class`,它就会返回该对象所属的类。 因此,我们看到 1、2、3…....等数字属于`Integer`类。 让我们重写其关键方法`+`。 Ruby 中使用加号将两个数字相加。 在 irb 中尝试这些示例

W
wizardforcel 已提交
530
```rb
W
init  
wizardforcel 已提交
531 532 533 534 535 536 537 538
>> 1 + 2
=> 3
>> 478 + 90
=> 568
```

因此,我们看到当有一个整数时,后面跟一个加号,另一个 Integer Ruby 解释器将这两个 Integer 相加,并作为结果返回。 现在让我们弄乱加号。 看一下 [overlay_methods_1.rb](code/override_methods_1.rb) ,在文本编辑器中将其键入并执行。

W
wizardforcel 已提交
539
```rb
W
init  
wizardforcel 已提交
540 541 542 543 544 545 546 547 548 549 550 551 552 553
# override_methods_2.rb

class Integer
  def + a
    416
  end
end

puts 3 + 5
puts 7 + 12
```

Result

W
wizardforcel 已提交
554
```rb
W
init  
wizardforcel 已提交
555 556 557 558
416
416
```

W
wizardforcel 已提交
559
看结果,你不惊讶吗? 当添加 3 和 5 时,结果为 416,添加 7 和 12 时,结果再次为 416。看一下 Fixnum 类中的代码。 为了使你的阅读更加方便,以下是代码:
W
init  
wizardforcel 已提交
560

W
wizardforcel 已提交
561
```rb
W
init  
wizardforcel 已提交
562 563 564 565 566 567 568
def + a
  416
end
```

在其中我们重新定义了 Integer 类中的方法`+`。 在其中,我们已经说过无论 a 的值是什么(即`+`符号右侧的数字),我们都必须返回 416 的值,因此 Ruby 解释器会简单地服从它。

W
wizardforcel 已提交
569
Integer 是 Ruby 的核心类,在许多编程语言(例如 Java)中,它都不愿意修改核心类,但 Ruby 允许。 许多撰写过有关 Ruby 的书的作者和编程专家都称此 Ruby 功能为一项危险功能,这确实是危险的,如果你确实在 Ruby 中修改了一个重要的类并且如果我们的代码被深埋在项目中,那么有时它可以 会导致程序中出现严重的逻辑错误,有时可能会浪费大量资源,因为人们需要埋头调试代码。 因此,在覆盖类中的方法之前,请先考虑一下,然后在确实有必要的情况下进行一些改进。
W
init  
wizardforcel 已提交
570 571 572 573 574

### 11.9。 超级功能

见下面的程序

W
wizardforcel 已提交
575
```rb
W
init  
wizardforcel 已提交
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
#!/usr/bin/ruby
# class_super.rb

class Rectangle

  def set_dimension length, breadth
    @length, @breadth = length, breadth
  end

  def area
    @length * @breadth
  end

end

class Square < Rectangle

  def set_dimension side_length
    super side_length, side_length
  end

end

square = Square.new
square.set_dimension 7
puts "Area: #{square.area}"
```

Output

W
wizardforcel 已提交
606
```rb
W
init  
wizardforcel 已提交
607 608 609
Area: 49
```

W
wizardforcel 已提交
610
在程序中,你会看到一个`Rectangle`类,在其中你会看到一个名为`set_dimension`的函数,如下所示。 此函数接收两个参数`length`和`breadth`,它们在此行`@length, @breadth = length, breadth`中分配给类变量`@length`和`@breadth`
W
init  
wizardforcel 已提交
611

W
wizardforcel 已提交
612
```rb
W
init  
wizardforcel 已提交
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
class Rectangle

        def set_dimension length, breadth
                @length, @breadth = length, breadth
        end

        def area
                @length * @breadth
        end

end
```

现在查看类`Square`。 `Square`继承了`Rectangle`(长度和宽度相等),但并非相反。 现在注意下面的代码

W
wizardforcel 已提交
628
```rb
W
init  
wizardforcel 已提交
629 630 631 632 633 634 635 636 637
class Square < Rectangle

  def set_dimension side_length
          super side_length, side_length
  end

end
```

W
wizardforcel 已提交
638
你可以看到`Square`类具有自己的`set_dimension`方法,现在看看它具有什么,它有了一个新东西,看看显示`super side_length, side_length`的行,在这里我们称为`super`的新方法。 `super`是一种特殊的方法,如果在`set_dimension`中调用它,它将查看父类是否具有同名的方法,如果是,则调用该方法。 因此,此处`super`将在`Rectangle`中称为`set_dimension`,并将`side_length`传递到`length`,从而将其设置为`@length`,将`side_length`传递到`breadth`,从而将其分别设置为`@breadth` 。
W
init  
wizardforcel 已提交
639 640 641 642 643 644 645

`@length`和`@breadth`相等的矩形是正方形! 不是吗 认为!!!

### 11.10。 私人,公共和继承保护

由于我们在谈论继承,因此现在更好了,因为我们看到了公共,私有和受保护方法的作用。 要了解它们,请键入 [public_private_protected_in_inheritance.rb](code/public_private_protected_in_inheritance.rb) 并执行它。

W
wizardforcel 已提交
646
```rb
W
init  
wizardforcel 已提交
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
# public_private_protected_in_inheritance.rb

class A
  def public_method
    puts "Class A public method"
  end

  private
  def private_method
    puts "Class A private method"
  end

  protected
  def protected_method
    puts "Class A protected method"
  end
end

class B < A
  def get_class_a_protected_method
    protected_method # implicit call
  end

  def get_class_a_private_method
    private_method # implicit call
  end
end

class C < A
  def get_class_a_protected_method
    self.protected_method # explicit call
  end

  def get_class_a_private_method
    self.private_method # explicit call
  end
end

a = A.new
a.public_method
# a.protected_method
# a.protected_method
b = B.new
b.get_class_a_protected_method
b.get_class_a_private_method
c = C.new
c.get_class_a_protected_method
# c.get_class_a_private_method
```

Output

W
wizardforcel 已提交
699
```rb
W
init  
wizardforcel 已提交
700 701 702 703 704 705
Class A public method
Class A protected method
Class A private method
Class A protected method
```

W
wizardforcel 已提交
706
你将获得如上所示的输出。 现在让我们分析程序。有一个名为`A`的类,它具有如下所示的公共方法
W
init  
wizardforcel 已提交
707

W
wizardforcel 已提交
708
```rb
W
init  
wizardforcel 已提交
709 710 711 712 713 714 715 716 717 718 719
class A
  def public_method
    puts "Class A public method"
  end

  ……….
end
```

如图所示的私有方法

W
wizardforcel 已提交
720
```rb
W
init  
wizardforcel 已提交
721 722 723 724 725 726 727 728 729 730 731 732 733 734
class A
  ...

  private
  def private_method
    puts "Class A private method"
  end

  ...
end
```

和受保护的方法

W
wizardforcel 已提交
735
```rb
W
init  
wizardforcel 已提交
736 737 738 739 740 741 742 743 744 745
class A
  ...

  protected
  def protected_method
    puts "Class A protected method"
  end
end
```

W
wizardforcel 已提交
746
如你从上面的示例中看到的代码,如果你输入关键字`private`,则在其下键入的内容将变为私有,对于受保护的内容也是如此。 我本可以编写如下所示的 public 方法
W
init  
wizardforcel 已提交
747

W
wizardforcel 已提交
748
```rb
W
init  
wizardforcel 已提交
749 750 751 752 753 754 755 756 757 758 759 760
class A
  public
  def public_method
    puts "Class A public method"
  end

  ……….
end
```

但是默认情况下,如果方法不属于私有方法或受保护方法,则该方法为公共方法,因此在此方法上方未包含任何内容。 现在让我们看一下 B 类

W
wizardforcel 已提交
761
```rb
W
init  
wizardforcel 已提交
762 763 764 765 766 767 768 769 770 771 772 773 774
class B < A
  def get_class_a_protected_method
    protected_method # implicit call
  end

  def get_class_a_private_method
    private_method # implicit call
  end
end
```

查看突出显示的代码。 在`get_class_a_protected_method`和`get_class_a_private_method`中,我们以隐式方式调用`protected_method`和`private_method`,并且在执行以下代码时

W
wizardforcel 已提交
775
```rb
W
init  
wizardforcel 已提交
776 777 778 779 780 781 782
b = B.new
b.get_class_a_protected_method
b.get_class_a_private_method
```

他们完美地工作。 现在让我们看 C 类,

W
wizardforcel 已提交
783
```rb
W
init  
wizardforcel 已提交
784 785 786 787 788 789 790 791 792 793 794
class C < A
  def get_class_a_protected_method
    self.protected_method # explicit call
  end

  def get_class_a_private_method
    self.private_method # explicit call
  end
end
```

W
wizardforcel 已提交
795
如你在上面的代码中看到的那样,在`get_class_a_protected_method`和`get_class_a_private_method`中,如上面的代码段所示,显式调用了 protected_method 和 private_method,现在在下面的这段代码中
W
init  
wizardforcel 已提交
796

W
wizardforcel 已提交
797
```rb
W
init  
wizardforcel 已提交
798 799 800 801 802 803 804 805 806
c = C.new
c.get_class_a_protected_method
# c.get_class_a_private_method
```

`c.get_class_a_private_method`将引发`NoMethodError`(无方法错误)异常,而`c.get_class_a_protected_method`将平稳运行。 因此,我们可以很清楚地看到,在显式调用中,无法访问 Parent 类的私有方法。 我鼓励人们取消注释上面的注释代码并执行它,并自己经历错误。

值得一提的是,在一种情况下,即使在显式调用期间,方法的私有状态也不起作用。 看下面的代码,键入并执行它。

W
wizardforcel 已提交
807
```rb
W
init  
wizardforcel 已提交
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
# private_attribute_writer.rb

class Parent
  private
  # we have a attribute writer private method
  def private_method= some_val
    puts some_val
  end
end

class Child < Parent
  def set_some_val
    self.private_method = "I know your secret!"
  end
end

Child.new.set_some_val
```

Output

W
wizardforcel 已提交
829
```rb
W
init  
wizardforcel 已提交
830 831 832 833 834 835 836
I know your secret!
```

令人惊讶的是,当执行`Child.new.set_some_val`时,实际上它以显式方式调用了 Parent 类中的`private_method`,但执行时却没有大惊小怪。 这是因为如上代码所示,Parent 类中的代码`private_method`是属性编写器,即以`=`符号结尾。 这是 Ruby 中的特例。

### 11.11。 延伸班

W
wizardforcel 已提交
837
Ruby 允许程序员(几乎)以任何想要的方式扩展预先存在的类,无论这些类是你编写的还是捆绑到 Ruby 语言本身中都没有关系。 在下面的示例中,我们将扩展 Integer 类以适合我们的需求。 在文本编辑器中键入程序并执行
W
init  
wizardforcel 已提交
838

W
wizardforcel 已提交
839
```rb
W
init  
wizardforcel 已提交
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
# extending_class_1.rb

class Integer
  def minute
    to_s.to_i * 60
  end

  def hour
    to_s.to_i.minute * 60
  end

  def day
    to_s.to_i.hour * 24
  end

  def week
    to_s.to_i.day * 7
  end
end

puts Time.now + 2.week
```

Result

W
wizardforcel 已提交
865
```rb
W
init  
wizardforcel 已提交
866 867 868 869 870
2018-10-10 10:27:37 +0530
```

该程序将当前时间与当前秒精确地相差 2 周,请注意,我们通过以下语句进行操作:

W
wizardforcel 已提交
871
```rb
W
init  
wizardforcel 已提交
872 873 874 875 876
puts Time.now + 2.week
```

`Time.now`获取当前的`Time`实例,并在其中添加`2.week`。 实际上,本机`Integer`类中没有名为`week`的方法,但是在程序中我们看到定义了`Integer`类,该类的方法名为`week`,当其调用时返回一周中的秒数 可以将其添加到时间对象或从中减去以获取过去和将来的时间。

W
wizardforcel 已提交
877
你可以类似的方式在 Ruby 中扩展任何类,几乎可以重写任何方法。 当然,一些程序员将其视为威胁,因为一些意外更改可能会在你的代码中引入错误,但是,如果你真的喜欢 Ruby,则无关紧要。
W
init  
wizardforcel 已提交
878 879 880 881 882

### 11.12。 反射

反射是计算机程序可以自行分析并随时进行修改的过程。 在接下来的页面中,我们将仅从头开始。

W
wizardforcel 已提交
883
我们将在 irb 中尝试这些示例,因此在你的终端中键入 irb –simple-prompt。 在下面的 irb 提示中,我声明了一个字符串变量`a`并将其设置为值`“Some string”`
W
init  
wizardforcel 已提交
884

W
wizardforcel 已提交
885
```rb
W
init  
wizardforcel 已提交
886 887 888 889 890 891
>> a = "Some string"
=> "Some string"
```

现在让我们看看可以使用变量`a`可用的方法。 为此,请在 irb 中键入`a.methods`

W
wizardforcel 已提交
892
```rb
W
init  
wizardforcel 已提交
893 894 895 896
>> a.methods
=> ["upcase!", "zip", "find_index", "between?", "to_f", "minmax", "lines", "sub", "methods", "send", "replace", "empty?", "group_by", "squeeze", "crypt", "gsub!", "taint", "to_enum", "instance_variable_defined?", "match", "downcase!", "take", "find_all", "min_by", "bytes", "entries", "gsub", "singleton_methods", "instance_eval", "to_str", "first", "chop!", "enum_for", "intern", "nil?", "succ", "capitalize!", "take_while", "select", "max_by", "chars", "tr!", "protected_methods", "instance_exec", "sort", "chop", "tainted?", "dump", "include?", "untaint", "each_slice", "instance_of?", "chomp!", "swapcase!", "drop", "equal?", "reject", "hex", "minmax_by", "sum", "hash", "private_methods", "all?", "tr_s!", "sort_by", "chomp", "upcase", "start_with?", "unpack", "succ!", "enum_slice", "kind_of?", "strip!", "freeze", "drop_while", "eql?", "next", "collect", "oct", "id", "slice", "casecmp", "grep", "strip", "any?", "delete!", "public_methods", "end_with?", "downcase", "%", "is_a?", "scan", "lstrip!", "each_cons", "cycle", "map", "member?", "tap", "type", "*", "split", "insert", "each_with_index", "+", "count", "lstrip", "one?", "squeeze!", "instance_variables", "__id__", "frozen?", "capitalize", "next!", "each_line", "rstrip!", "to_a", "enum_cons", "ljust", "respond_to?", "upto", "display", "each", "inject", "tr", "method", "slice!", "class", "reverse", "length", "enum_with_index", "rpartition", "rstrip", "<=>", "none?", "instance_variable_get", "find", "==", "swapcase", "__send__", "===", "min", "each_byte", "extend", "to_s", "rjust", "index", ">=", "size", "reduce", "tr_s", "<=", "clone", "reverse_each", "to_sym", "bytesize", "=~", "instance_variable_set", "<", "detect", "max", "each_char", ">", "to_i", "center", "inspect", "[]", "reverse!", "rindex", "partition", "delete", "[]=", "concat", "sub!", "dup", "object_id", "<<"]
```

W
wizardforcel 已提交
897
如你所见,`a.methods`返回可以在`String`上使用的方法,即函数。 接下来,我们将尝试找出`a`属于什么类或类型。 我们当然知道它属于`String`类型,但是为了学习将`a.class`类型编程为 irb
W
init  
wizardforcel 已提交
898

W
wizardforcel 已提交
899
```rb
W
init  
wizardforcel 已提交
900 901 902 903 904 905 906 907
>> a.class
=> String
```

并忠实地返回 a 属于`String`类型。

很好,我们已经看到了一些有关反射的东西。 为了更好地理解它,让我们定义我们自己的类,看看反射如何作用于它。 在文本编辑器中输入以下程序 [Reflection.rb](code/reflection.rb) 并执行它。

W
wizardforcel 已提交
908
```rb
W
init  
wizardforcel 已提交
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
# reflection.rb

class Someclass
  attr_accessor :a, :b

  private
  # A dummy private method
  def private_method
  end

  protected
  # A dummy protected method
  def protected_method
  end

  public
  # A dummy public method
  def public_method
  end
end

something = Someclass.new
something.a = 'a'
something.b = 123
puts "something belongs to #{something.class}"
puts
puts "something has the following instance variables:"
puts something.instance_variables.join(', ')
puts
puts "something has the following methods:"
puts something.methods.join(', ')
puts
puts "something has the following public methods:"
puts something.public_methods.join(', ')
puts
puts "something has the following private methods:"
puts something.private_methods.join(', ')
puts
puts "something has the following protected methods:"
puts something.protected_methods.join(', ')
```

Output

W
wizardforcel 已提交
953
```rb
W
init  
wizardforcel 已提交
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
something belongs to Someclass

something has the following instance variables:
@a, @b

something has the following methods:
inspect, protected_method, tap, clone, public_methods, __send__, object_id, instance_variable_defined?, equal?, freeze, extend, send, methods, public_method, hash, dup, to_enum, instance_variables, eql?, a, instance_eval, id, singleton_methods, a=, taint, enum_for, frozen?, instance_variable_get, instance_of?, display, to_a, method, b, type, instance_exec, protected_methods, ==, b=, ===, instance_variable_set, kind_of?, respond_to?, to_s, class, __id__, tainted?, =~, private_methods, untaint, nil?, is_a?

something has the following public methods:
inspect, tap, clone, public_methods, __send__, object_id, instance_variable_defined?, equal?, freeze, extend, send, methods, public_method, hash, dup, to_enum, instance_variables, eql?, a, instance_eval, id, singleton_methods, a=, taint, enum_for, frozen?, instance_variable_get, instance_of?, display, to_a, method, b, type, instance_exec, protected_methods, ==, b=, ===, instance_variable_set, kind_of?, respond_to?, to_s, class, __id__, tainted?, =~, private_methods, untaint, nil?, is_a?

something has the following private methods:
exit!, chomp!, initialize, fail, print, binding, split, Array, format, chop, iterator?, catch, readlines, trap, remove_instance_variable, getc, singleton_method_added, caller, putc, autoload?, proc, chomp, block_given?, throw, p, sub!, loop, syscall, trace_var, exec, Integer, callcc, puts, initialize_copy, load, singleton_method_removed, exit, srand, lambda, global_variables, gsub!, untrace_var, open, `, system, Float, method_missing, singleton_method_undefined, sub, abort, gets, require, rand, test, warn, eval, local_variables, chop!, scan, raise, printf, set_trace_func, private_method, fork, String, select, sleep, gsub, sprintf, autoload, readline, at_exit, __method__

something has the following protected methods:

protected_method
```

W
wizardforcel 已提交
973
如上所示,你必须具有相当大的输出。 现在让我们遍历代码。 首先,我们定义一个名为`Someclass`的类,其中有两个属性`a`和`b`。 我们有一个称为`private_method`的私有方法,一个名为`protected_method`的受保护方法以及名为`public_method`的公共方法。
W
init  
wizardforcel 已提交
974 975 976

定义完类后,我们创建一个类型为`Someclass`的名为`something`的变量,并在以下几行中为其属性赋值

W
wizardforcel 已提交
977
```rb
W
init  
wizardforcel 已提交
978 979 980 981 982 983 984
something = Someclass.new
something.a = 'a'
something.b = 123
```

接下来,我们要求 ruby 解释器使用忠实执行的以下语句`puts "something belongs to #{something.class}"`打印变量`something`的类,因此得到以下输出:

W
wizardforcel 已提交
985
```rb
W
init  
wizardforcel 已提交
986 987 988 989 990
something belongs to Someclass
```

接下来,我们想知道类型为`Someclass`的对象`something`是否具有任何实例变量。 要知道它,我们使用以下代码:

W
wizardforcel 已提交
991
```rb
W
init  
wizardforcel 已提交
992 993 994 995 996 997
puts "something has the following instance variables:"
puts something.instance_variables.join(', ')
```

为此,我们得到以下输出

W
wizardforcel 已提交
998
```rb
W
init  
wizardforcel 已提交
999 1000 1001 1002 1003 1004
something has the following instance variables:
@a, @b
```

接下来,我们想知道有什么方法可以使用。 要知道我们可以使用`methods`函数,因此我们编写以下代码:

W
wizardforcel 已提交
1005
```rb
W
init  
wizardforcel 已提交
1006 1007 1008 1009 1010 1011 1012 1013
puts "something has the following methods:"
puts something.methods.join(', ')
```

在上面的代码中`something.methods`返回一个方法数组,必须将其转换为由`join`方法完成的字符串。 数组的元素由传递给`join`方法的 String 连接。 请注意,还有比我们定义的方法更多的方法,这是因为甚至`Someclass`的类型也都是`Object` &lt;sup class="footnote"&gt;[ [31](#_footnotedef_31 "View footnote.") ]&lt;/sup&gt; 类型,它本身也有许多方法。 在 Ruby 中,一切都是对象。

任何 Object 的方法和 public_methods 都会返回相同的结果。 所以我们将跳过关于这些的讨论

W
wizardforcel 已提交
1014
```rb
W
init  
wizardforcel 已提交
1015 1016 1017 1018 1019 1020 1021 1022
puts "something has the following public methods:"
puts something.public_methods.join(', ')
```

陈述

接下来,我们想知道`Someclass`中的私有,公共和受保护方法是什么,由于`something`属于`Someclass`,因此可以使用`private_methods`函数获得私有方法,因此通过给出以下语句

W
wizardforcel 已提交
1023
```rb
W
init  
wizardforcel 已提交
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
puts "something has the following private methods:"
puts something.private_methods.join(', ')
```

我们可以在某些类中获得私有方法。

通过使用`protected_methods`函数也可以得到类似的受保护方法,由于我的懒惰,我将不再讨论。

### 11.13。 封装形式

W
wizardforcel 已提交
1034
你可能在生活中的某个时间点服用了胶囊片剂。 在其中,药物包装在明胶胶囊内。 当你用水服用时,它会滑到你的胃中,在那里水分解出明胶层,释放出其中的药物,从而治愈你的身体疾病。 如果你尝试在没有胶囊的情况下吞服药物,那将是一种痛苦的经历。
W
init  
wizardforcel 已提交
1035

W
wizardforcel 已提交
1036
现代编程语言以类似的方式允许你隐藏不需要的细节,并让你的同伴程序员仅查看所需的细节。 这项技术称为封装,如果正确实施,则会产生清晰的代码,并且易于使用。
W
init  
wizardforcel 已提交
1037

W
wizardforcel 已提交
1038
封装的另一个很好的例子是你的汽车。 在引擎盖下,你的汽车有成千上万个可以这种方式旋转的零件,但是你要做的就是打开钥匙并操作方向盘和踏板以获得驾驶体验。 你无需理会引擎盖内部发生的事情。
W
init  
wizardforcel 已提交
1039 1040 1041

让我们看一个小例子,它将向我们解释封装的工作原理。 在文本编辑器中键入程序 [encapsulation.rb](code/encapsulation.rb) 并执行它。

W
wizardforcel 已提交
1042
```rb
W
init  
wizardforcel 已提交
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
# encapsulation.rb

class Human
  attr_reader :firstname, :lastname

  def name=(name)
    @firstname, @lastname = name.split
  end
end

guy = Human.new
guy.name = "Ramanuja Iyengaar"
puts "First name: #{guy.firstname}"
puts "Last name: #{guy.lastname}"
```

Output

W
wizardforcel 已提交
1061
```rb
W
init  
wizardforcel 已提交
1062 1063 1064 1065 1066 1067
First name: Ramanuja
Last name: Iyengaar
```

这样我们得到的人的名字叫 Ramanuja,姓氏叫 Iyengar。 由于以下陈述,将这两行打印出来

W
wizardforcel 已提交
1068
```rb
W
init  
wizardforcel 已提交
1069 1070 1071 1072 1073 1074
puts "First name: #{guy.firstname}"
puts "Last name: #{guy.lastname}"
```

在这些语句之前,请参见两行。 首先,通过编写`guy = Human.new`声明一个类型为`Human`的名为`guy`的新变量,接着设置`guy.name = "Ramanuja Iyengaar"``,但是在第一个`puts`语句中,我们将调用`guy.firstname`,在下一个中,我们将`guy.lastname`,我们得到了答案。 这是因为在程序内的`name`方法中,请参见此代码

W
wizardforcel 已提交
1075
```rb
W
init  
wizardforcel 已提交
1076 1077 1078 1079 1080 1081 1082
def name=(name)
  @firstname, @lastname = name.split
end
```

我们将其拆分,并使用以下代码将空格前的单词分配为`@firstname`,将空格后的单词分配为`@lastname`:

W
wizardforcel 已提交
1083
```rb
W
init  
wizardforcel 已提交
1084 1085 1086 1087 1088 1089 1090 1091 1092
@firstname, @lastname = name.split
```

因此,当我们调用`guy.firstname`和`guy.lastname`时,它们会如实打印。 请注意,在类外,我们从未设置过`@first_name`和`@last_name`,它们是完全由我们封装的。

有人可能想知道`attr_reader :firstname, :lastname`语句的作用是什么? 它声明了两个变量`@first_name`和`@last_name`。 `attr_reader`表示这两个变量只能由类外部的程序读取,换句话说,如果我们尝试设置`guy.first_name = “Ramanuja”`,则 Ruby 解释器将抛出错误。

### 11.14。 多态性

W
wizardforcel 已提交
1093
Poly 表示很多,而 morphis 表示形式。 我认为它是希腊语还是拉丁语,谁在乎? 在编程语言中,你可以使用一件事情来做很多事情,下面举几个例子。 让我们采取谦虚的加号。 当我们取`“Hello ”`和`“World!”`并在它们之间加一个加号时,输出为`“Hello World!”`。 在技​​术交流中,我们称这种串联(连接在一起)。 这是 irb 示例:
W
init  
wizardforcel 已提交
1094

W
wizardforcel 已提交
1095
```rb
W
init  
wizardforcel 已提交
1096 1097 1098 1099 1100 1101
>> "Hello " + "World!"
=> "Hello World!"
```

现在让我们在数字上使用此加号。 现在,我们将其停留在 134 和 97 之间。执行此操作时,答案为 231,而不是 13497。为什么? 这是因为加号被卡在不同事物之间时,训练它可以做不同的事情。

W
wizardforcel 已提交
1102
```rb
W
init  
wizardforcel 已提交
1103 1104 1105 1106
>> 134 + 97
=> 231
```

W
wizardforcel 已提交
1107
当你将其插入字符串之间时,它们会连接在一起;当你将其插入数字之间时,则会添加它们。 因此,操作员会根据情况采取多种形式或进行不同的操作。
W
init  
wizardforcel 已提交
1108 1109 1110

以类似的方式,如果将字符串乘以数字会发生什么。 好吧,如下所示

W
wizardforcel 已提交
1111
```rb
W
init  
wizardforcel 已提交
1112 1113 1114 1115 1116 1117
>> "Hello" * 5
=> "HelloHelloHelloHelloHello"
```

我们看到字符串被打印的次数。 因此,将“ Hello”乘以 5 可打印“ Hello”五次。 在下一个示例中,我们将值 6 分配给名为 hello 的变量,并将其乘以 5

W
wizardforcel 已提交
1118
```rb
W
init  
wizardforcel 已提交
1119 1120 1121 1122 1123 1124
>> hello = 6
=> 6
>> hello * 5
=> 30
```

W
wizardforcel 已提交
1125
由于`hello`是带有数字的变量,因此将其与数字相乘会得到一个数字。 因此,你甚至可以看到乘法运算符根据情况采用多种形式或不同的函数。 就像这样,一个警察在家时对家人友善,当他受到轰击时,他的举止却有所不同。
W
init  
wizardforcel 已提交
1126

W
wizardforcel 已提交
1127
长度运算符/函数以类似的方式,当你找出字符串的长度时,它会告诉其中的字符数。
W
init  
wizardforcel 已提交
1128

W
wizardforcel 已提交
1129
```rb
W
init  
wizardforcel 已提交
1130 1131 1132 1133
>> "some text".length
=> 9
```

W
wizardforcel 已提交
1134
当你找到数组的长度时,它会告诉该数组具有的元素数。
W
init  
wizardforcel 已提交
1135

W
wizardforcel 已提交
1136
```rb
W
init  
wizardforcel 已提交
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
>> [5, 7, "some text", Time.now].length
=> 4
```

因此我们看到在 Ruby 中,一件事情可以做不同的事情,就像现实世界中的对象 &lt;sup class="footnote"&gt;[ [32](#_footnotedef_32 "View footnote.") ]&lt;/sup&gt; 一样。

### 11.15。 类常量

就像任何其他编程语言一样,在 Ruby 中的类中可以有一个常量值。 让我们开始行动,看看下面的程序 [class_constant.rb](code/class_constant.rb) ,其源代码是这样的

W
wizardforcel 已提交
1147
```rb
W
init  
wizardforcel 已提交
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159
#!/usr/bin/ruby
# class_constant.rb

class Something
  Const = 25
end

puts Something::Const
```

Output

W
wizardforcel 已提交
1160
```rb
W
init  
wizardforcel 已提交
1161 1162 1163
25
```

W
wizardforcel 已提交
1164
注意在`Something`类中的代码段,你会看到`Const = 25`语句。 如果你读得很好,你可能会意识到 Ruby 中的常量以大写字母开头。 在类`Something`中,我们声明了一个常量名称`Const`并将其分配给 25。
W
init  
wizardforcel 已提交
1165 1166 1167

请注意,语句`puts Something::Const`和`puts`用于打印抛出的几乎所有内容。 在这里,我们抛出`Something::Const`,它忠实地打印出恒定值。 因此,可以通过`&lt;class_name&gt;::&lt;constant_name&gt;`访问类常量,这就是访问类常量的方式。 让我们看看类实例如何访问类常量。 键入程序 class_constant_1.rb

W
wizardforcel 已提交
1168
```rb
W
init  
wizardforcel 已提交
1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
#!/usr/bin/ruby
# class_constant.rb

class Something
  Const = 25
end

puts Something::Const
```

Output

W
wizardforcel 已提交
1181
```rb
W
init  
wizardforcel 已提交
1182 1183 1184 1185
25
class_constant_1.rb:10: undefined method `Const' for #<Something:0xb745eb58> (NoMethodError)
```

W
wizardforcel 已提交
1186
因此,在上面的程序中,我们声明了一个类别为`Something`的变量`s`。 在`puts s.Const`行中,我们尝试通过其实例变量 s 访问某个内部的常量值,并且出现 No Method Error,或者 Ruby 解释器认为`Const`是一种方法,因为我们使用了`s.Const`。 要解决此问题,你可以编写一个名为`Const`的方法,然后按 [class_constant_2.rb](code/class_constant_2.rb) 中所示的方法进行调用
W
init  
wizardforcel 已提交
1187

W
wizardforcel 已提交
1188
```rb
W
init  
wizardforcel 已提交
1189 1190 1191 1192 1193
include::code/class_constant_2.rb
```

Output

W
wizardforcel 已提交
1194
```rb
W
init  
wizardforcel 已提交
1195 1196 1197 1198 1199 1200
25
25
```

因此,定义方法 &lt;sup class="footnote"&gt;[ [33](#_footnotedef_33 "View footnote.") ]&lt;/sup&gt; 并从中返回`Const`解决了该问题。

W
wizardforcel 已提交
1201
有人可能会认为可以使用实例变量通过使用双冒号(::)而不是 [class_constant_3.rb](code/class_constant_3.rb) 中所示的点运算符来访问类常量值,正如你从其输出中看到的那样,它将无法正常工作
W
init  
wizardforcel 已提交
1202

W
wizardforcel 已提交
1203
```rb
W
init  
wizardforcel 已提交
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
#!/usr/bin/ruby
# class_constant_3.rb

class Something
  Const = 25

  def Const
    Const
  end
end

puts Something::Const
s = Something.new
puts s::Const
```

Output

W
wizardforcel 已提交
1222
```rb
W
init  
wizardforcel 已提交
1223 1224 1225 1226 1227 1228
25
class_constant_3.rb:14: #<Something:0xb74029fc> is not a class/module (TypeError)
```

### 11.16。 功能别名

W
wizardforcel 已提交
1229
有时你可能已经编写了一个函数,并且可能希望将其重命名为某种东西,那时候该怎么办? 如果你在许多地方都使用过该功能,并且必须重命名,那么你必须在许多地方不断更改名称。 幸运的是,ruby 提供了一个名为`alias`的关键字,你可以使用该关键字为函数设置另一个名称。 看一下下面的程序,尤其是这一行`alias :shout :make_noise`。
W
init  
wizardforcel 已提交
1230

W
wizardforcel 已提交
1231
```rb
W
init  
wizardforcel 已提交
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
# function_alias.rb

class Something
  def make_noise
    puts "AAAAAAAAAAAAAAHHHHHHHHHHHHHH"
  end

  alias :shout :make_noise
end

Something.new.shout
```

Output

W
wizardforcel 已提交
1247
```rb
W
init  
wizardforcel 已提交
1248 1249 1250
AAAAAAAAAAAAAAHHHHHHHHHHHHHH
```

W
wizardforcel 已提交
1251
如你所见,我们将其称为`Something.new.shout`,并且由于我们已将`make_noise`别名为`shout`,因此将调用`make_noise`。
W
init  
wizardforcel 已提交
1252 1253 1254 1255 1256

### 11.17。 匿名类

类可以没有名称,这些类称为匿名类。 查看下面的示例,键入并执行:

W
wizardforcel 已提交
1257
```rb
W
init  
wizardforcel 已提交
1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271
# anonymous_class.rb

person = Class.new do
  def say_hi
    'Hi'
  end
end.new

puts person.say_hi
puts person.class
```

Output

W
wizardforcel 已提交
1272
```rb
W
init  
wizardforcel 已提交
1273 1274 1275 1276
Hi
#<Class:0x0000000002696840>
```

W
wizardforcel 已提交
1277
因此,让我们看看它是如何工作的。 首先,你有一个作为类实例的变量`person`,我们将其分配给`person = Person.new`而不是`person = Person.new`,如下所示。 也就是说,我们正在动态创建一个没有任何名称的新类。 因此它是匿名的。
W
init  
wizardforcel 已提交
1278

W
wizardforcel 已提交
1279
```rb
W
init  
wizardforcel 已提交
1280 1281 1282 1283 1284 1285 1286 1287
person = Class.new do **(1)**
  ......
end.new **(2)**
```

| **1** | 创建一个匿名类。 |
| **2** | 一个匿名类,在其`end`应该已经用`new`初始化之后 |

W
wizardforcel 已提交
1288
如果你可以从上面的代码中立即看到匿名类的定义,则必须使用`.new`立即对其进行初始化。 这也使匿名类仅坚持一个变量。 现在,我们将所需的内容填写到匿名类中,如下所示:
W
init  
wizardforcel 已提交
1289

W
wizardforcel 已提交
1290
```rb
W
init  
wizardforcel 已提交
1291 1292 1293 1294 1295 1296 1297 1298 1299
person = Class.new do
  def say_hi
    'Hi'
  end
end.new
```

因此,当我们调用`person.say_hi`时,它返回要打印的`'Hi'`,但是当我们调用`person.class`而不是打印类名时,它会打印出类似这样的内容`#&lt;Class:0x0000000002696840&gt;` &lt;sup class="footnote"&gt;[ [34](#_footnotedef_34 "View footnote.") ]&lt;/sup&gt; 。 由于该类没有名称,因此没有要打印的名称。

W
wizardforcel 已提交
1300
如果你在下面的 irb 中看到我编写的代码,我们可以看到调用`a.class`时会显示`A`,即类名,但在上述情况下,我们没有名称。
W
init  
wizardforcel 已提交
1301

W
wizardforcel 已提交
1302
```rb
W
init  
wizardforcel 已提交
1303 1304 1305 1306 1307 1308 1309 1310
>> class A; end
 => nil
>> a = A.new
>> a.class
 => A
```

我认为`#&lt;Class:0x0000000002696840&gt;`中的`0x…​.`代表类存储在内存中的地址位置。