ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 11.类&对象 ### 11.1。 创建一个正方形 可以将类视为一个名称捆绑在一起的变量和函数。 为了说明类,让我们看一个不起眼的对象,叫做 Square。 正方形是一个非常简单的几何形状,具有四个等长的边,如何在 Ruby 程序中表示这个正方形? 现在,我们编写一个称为 Square 的空类,如下所示: ```rb #square.rb class Square end ``` 我们写了一个空课。 `class`一词表明我们正在编写一个类的定义。 class 关键字之后是类的名称,在本例中为`Square`。 必须注意,Ruby 中的类名称必须以大写字母 &lt;sup class="footnote"&gt;[ [28](#_footnotedef_28 "View footnote.") ]&lt;/sup&gt; 开头。 一个正方形有四个边,都长相同。 现在,我们将一个名为`side_length`的变量放入正方形。 ```rb #square.rb class Square attr_accessor :side_length end ``` 你可能会问`attr_accessor`是什么? 它代表`attribute accessor`,使你可以轻松获取和设置`side_length`。 让我们使用这个正方形类,看看它是如何工作的。 修改上面的代码,如下所示: ```rb #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 ``` 当你执行以上代码时,这将是你得到的结果 ```rb Side length of s1 = 5 ``` 让我们遍历新添加的代码,走线 ```rb s1 = Square.new ``` 在上面的语句中,我们创建一个新的 Square 并将其参数存储到名为`s1`的变量中。 可以使用`&lt;class name&gt;.new`创建一个新的类实例。 创建一个新的正方形后,我们现在可以使用点运算符“。”访问其`side_length`。 因此,我们首先使用以下语句将`side_length`设置为五个单位 ```rb s1.side_length = 5 ``` 现在已经分配了`side_length`,我们可以将其用于任何目的。 现在,我们只需使用以下语句即可打印正方形的`side_length`: ```rb puts "Side length of s1 = #{s1.side_length}" ``` ### 11.2。 班级职能 我们有一个名为`Square`的类,它具有一个名为`side_length`的属性。 使用`side_length`,我们可以找到正方形区域,其周长和对角线长度。 在此示例中,我将查找面积和周长。 因此,让我们添加两个函数来查找`area`和`perimeter`。 如下所示修改代码(我将修改后的代码保存在名为 [square_1.rb](code/square_1.rb) 的文件中) ```rb #square_1.rb class Square attr_accessor :side_length def area @side_length * @side_length end def perimeter 4 * @side_length end end ``` 在上面的代码中,你看到我添加了两个函数,一个名为`area`,另一个名为`perimeter`,分别计算并返回正方形的面积和周长。 这些函数与我们之前创建的任何其他函数非常相似,只是现在将其放置在类中。 让我们编写一些其他代码来利用我们添加的新功能,只需修改程序,使其看起来像下面的代码 ```rb #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}" ``` 运行示例,这是你将得到的输出 ```rb Area: 25 Perimeter: 20 ``` 解释很简单。 在以下几行 ```rb a = Square.new a.side_length = 5 ``` 我们声明了一个新的`Square`,并已将`side_length`分配为 5 个单位。 在下面的几行中,我们只需打印出`a.area`和`a.perimeter`的值 ```rb puts "Area: #{a.area}" puts "Perimeter: #{a.perimeter}" ``` 了解我们如何嵌入“ a”的面积和周长的值(在上面的代码中)。 如果你正在阅读这本书,那么对你来说必须是新的一件事如下所示: ```rb def area @side_length * @side_length end ``` 我们知道 square 具有一个名为`side_length`的属性,该属性由语句`attr_accessor :side_length`定义,正如上面突出显示的代码所示,我们使用了`@side_length`而不是`side_length`,这是因为在类内部,类变量是 带有`@`(at)符号的前缀。 这有助于我们在类变量和具有相同名称的局部变量或函数之间进行标识。 ### 11.3。 初始化程序或构造函数 在前面处理正方形的示例中,你是否想过当你说`s = Square.new`时会发生什么? 好吧,在这种情况下,将创建一个新的`Square`并将其放入变量中。 如果有人问天气问题,当 Square 初始化时我们可以做什么? 答案是肯定的。 你所要做的就是将代码放入称为`initialize`的函数中,只要有`&lt;class name&gt;.new call`,就会调用此函数 看一下示例 [square2.rb](code/square2.rb) ,仔细看下面的代码,我们定义了一个名为`initialize`的函数,该函数接受一个名为`side_length`的参数,其默认值为零。 如果指定了`side_length`,则它将平方类中的`@side_length`属性设置为该值,否则`@side_length`的默认值为零。 在文本编辑器中输入 [square2.rb](code/square2.rb) 并执行 ```rb #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" ``` 输出量 ```rb Area of s1 is 16 squnits Perimeter of s2 is 20 units ``` 在程序中集中于以下几行 ```rb 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 个单位。 在其余的代码中 ```rb puts "Area of s1 is #{s1.area} squnits" puts "Peimeter of s2 is #{s2.perimeter} units" ``` 我们打印`Square` `s1`的面积和`Square` `s2`的周长,以产生所需的输出。 ### 11.4。 未公开的实例变量 不需要使用`attr_accessor`公开实例变量,它可以隐藏,可以从函数中设置和调用,如图所示 ```rb # 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 ```rb Karthik and Chubby are best friends. ``` 在上面的示例中,注意`Human`类的(实例)确实使用了名为`@name`的变量,但你可以将其设置为`a.name =`,而不能像`a.name`那样获取它,因为它没有暴露给 使用`attr_accessor`的外部世界。 因此,在 Ruby 类中,你可以在类中具有任意数量的隐藏变量,而无需外界对此有所了解。 那不漂亮吗? ### 11.5。 私人方法 默认情况下,类中的方法或函数是公共的(可以在类范围之外访问),如果你不希望类外的程序访问它们,则可以将其设为私有。 让我们创建一个名为`Human`的类,并在其中放入一个私有方法,让我们尝试从该类外部访问它,然后看看会发生什么。 输入程序并执行 ```rb # 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 ``` 上面的程序在执行时产生以下结果 ```rb 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`),这意味着被调用的方法在类中不存在。 这并不意味着计算机在撒谎,而是在安全地保密。 在编程时,你只让程序的某些部分对其他人可见,这有助于使界面保持简单,并仅向用户提供他们真正需要编写代码的资源,这种隐藏不必要的东西使编程变得不复杂。 有人可能会问,如果没有办法访问私有方法,那为什么我们需要它呢? 好了,你可以在下面的示例中间接访问它。 输入并执行 ```rb # 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 ``` 结果就是这样 ```rb 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 下面显示的程序并执行 ```rb # 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" ``` 执行以上程序时,将得到以下结果 ```rb Created 2 robots Created 5 robots ``` 让我们看看`initialize`方法,并分析我们如何跟踪已创建的机器人数量。 在语句中创建第一个机器人时 ```rb r1 = Robot.new ``` 由于第一次没有定义变量`@@robot_count`,因此程序控制转到`initialize`方法,因此下面的`if`语句中的条件 ```rb if defined?(@@robot_count) @@robot_count += 1 else @@robot_count = 1 end ``` 失败,代码转到 else 部分,`@@robot_count = 1`定义了变量`@@robot_count`并初始化为值 1。 在第二条语句中,我们使用以下命令创建名为 r2 的机器人 ```rb r2 = Robot.new ``` 控件再次进入`initialize`方法,`if`语句通过,因为在创建`r1`时已经定义了`@@robot_count`,现在`@@robot_count`递增 1,现在变为 2。 接下来是我们称为`Robot.robots_created`的`puts`语句,该语句仅返回`@@robot_count`,因此将输出 2。 以下语句中的下一步: ```rb r3, r4, r5 = Robot.new, Robot.new, Robot.new ``` 我们创建了三个新的机器人`r3`,`r4`和`r5`。 现在``@@robot_count`将增加到 5。在下一个`puts`语句中,将打印结果。 故事的寓意是这样,课堂方法有一个`self`。 (自点)之前,类变量前面有两个@(`@@`)。 很好,希望一切对读者都适用。 为什么现在我们对`robot_count`变量使用`attr_reader`,以便我们的程序变得简化,如下所示。 输入并执行。 ```rb # 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; 。 当人类从猴子进化而来时,他从猴子那里继承了很多东西,例如我们看起来像猴子,不相信吗? 只是去站在镜子前面! 好的,在编程世界中,我们有一个称为继承的事物,其中一个类可以具有另一类的属性,而只需很少(或有时是极端的)变化。 让我告诉你一个数学真理,“正方形是所有边都相等的矩形”,不是吗? 所有正方形都是矩形,但并非所有矩形都是正方形。 我们将使用这些东西来编写我们的下一个程序 [Inheritance.rb](code/inheritance.rb) 。 在文本编辑器中编写程序并执行。 ```rb # 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}" ``` 执行后,上面的程序将产生以下结果 ```rb 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;`(小于)符号,如下所示 ```rb 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) ```rb #!/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 ``` 结果 ```rb 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`类中查找该方法。 我们将把覆盖概念更进一步,知道 Ruby 中的一切都是对象。 Ruby 纯粹是一种面向对象的编程语言。 拍摄你的 irb 并输入以下内容 ```rb >> "Something".class => String >> 1.class => Integer >> 3.14278.class => Float ``` 在 Ruby 中,只要在对象后放置`.class`,它就会返回该对象所属的类。 因此,我们看到 1、2、3…....等数字属于`Integer`类。 让我们重写其关键方法`+`。 Ruby 中使用加号将两个数字相加。 在 irb 中尝试这些示例 ```rb >> 1 + 2 => 3 >> 478 + 90 => 568 ``` 因此,我们看到当有一个整数时,后面跟一个加号,另一个 Integer Ruby 解释器将这两个 Integer 相加,并作为结果返回。 现在让我们弄乱加号。 看一下 [overlay_methods_1.rb](code/override_methods_1.rb) ,在文本编辑器中将其键入并执行。 ```rb # override_methods_2.rb class Integer def + a 416 end end puts 3 + 5 puts 7 + 12 ``` Result ```rb 416 416 ``` 看结果,你不惊讶吗? 当添加 3 和 5 时,结果为 416,添加 7 和 12 时,结果再次为 416。看一下 Fixnum 类中的代码。 为了使你的阅读更加方便,以下是代码: ```rb def + a 416 end ``` 在其中我们重新定义了 Integer 类中的方法`+`。 在其中,我们已经说过无论 a 的值是什么(即`+`符号右侧的数字),我们都必须返回 416 的值,因此 Ruby 解释器会简单地服从它。 Integer 是 Ruby 的核心类,在许多编程语言(例如 Java)中,它都不愿意修改核心类,但 Ruby 允许。 许多撰写过有关 Ruby 的书的作者和编程专家都称此 Ruby 功能为一项危险功能,这确实是危险的,如果你确实在 Ruby 中修改了一个重要的类并且如果我们的代码被深埋在项目中,那么有时它可以 会导致程序中出现严重的逻辑错误,有时可能会浪费大量资源,因为人们需要埋头调试代码。 因此,在覆盖类中的方法之前,请先考虑一下,然后在确实有必要的情况下进行一些改进。 ### 11.9。 超级功能 见下面的程序 ```rb #!/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 ```rb Area: 49 ``` 在程序中,你会看到一个`Rectangle`类,在其中你会看到一个名为`set_dimension`的函数,如下所示。 此函数接收两个参数`length`和`breadth`,它们在此行`@length, @breadth = length, breadth`中分配给类变量`@length`和`@breadth` ```rb class Rectangle def set_dimension length, breadth @length, @breadth = length, breadth end def area @length * @breadth end end ``` 现在查看类`Square`。 `Square`继承了`Rectangle`(长度和宽度相等),但并非相反。 现在注意下面的代码 ```rb class Square < Rectangle def set_dimension side_length super side_length, side_length end end ``` 你可以看到`Square`类具有自己的`set_dimension`方法,现在看看它具有什么,它有了一个新东西,看看显示`super side_length, side_length`的行,在这里我们称为`super`的新方法。 `super`是一种特殊的方法,如果在`set_dimension`中调用它,它将查看父类是否具有同名的方法,如果是,则调用该方法。 因此,此处`super`将在`Rectangle`中称为`set_dimension`,并将`side_length`传递到`length`,从而将其设置为`@length`,将`side_length`传递到`breadth`,从而将其分别设置为`@breadth` 。 `@length`和`@breadth`相等的矩形是正方形! 不是吗 认为!!! ### 11.10。 私人,公共和继承保护 由于我们在谈论继承,因此现在更好了,因为我们看到了公共,私有和受保护方法的作用。 要了解它们,请键入 [public_private_protected_in_inheritance.rb](code/public_private_protected_in_inheritance.rb) 并执行它。 ```rb # 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 ```rb Class A public method Class A protected method Class A private method Class A protected method ``` 你将获得如上所示的输出。 现在让我们分析程序。有一个名为`A`的类,它具有如下所示的公共方法 ```rb class A def public_method puts "Class A public method" end ………. end ``` 如图所示的私有方法 ```rb class A ... private def private_method puts "Class A private method" end ... end ``` 和受保护的方法 ```rb class A ... protected def protected_method puts "Class A protected method" end end ``` 如你从上面的示例中看到的代码,如果你输入关键字`private`,则在其下键入的内容将变为私有,对于受保护的内容也是如此。 我本可以编写如下所示的 public 方法 ```rb class A public def public_method puts "Class A public method" end ………. end ``` 但是默认情况下,如果方法不属于私有方法或受保护方法,则该方法为公共方法,因此在此方法上方未包含任何内容。 现在让我们看一下 B 类 ```rb 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`,并且在执行以下代码时 ```rb b = B.new b.get_class_a_protected_method b.get_class_a_private_method ``` 他们完美地工作。 现在让我们看 C 类, ```rb 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 ``` 如你在上面的代码中看到的那样,在`get_class_a_protected_method`和`get_class_a_private_method`中,如上面的代码段所示,显式调用了 protected_method 和 private_method,现在在下面的这段代码中 ```rb 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 类的私有方法。 我鼓励人们取消注释上面的注释代码并执行它,并自己经历错误。 值得一提的是,在一种情况下,即使在显式调用期间,方法的私有状态也不起作用。 看下面的代码,键入并执行它。 ```rb # 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 ```rb I know your secret! ``` 令人惊讶的是,当执行`Child.new.set_some_val`时,实际上它以显式方式调用了 Parent 类中的`private_method`,但执行时却没有大惊小怪。 这是因为如上代码所示,Parent 类中的代码`private_method`是属性编写器,即以`=`符号结尾。 这是 Ruby 中的特例。 ### 11.11。 延伸班 Ruby 允许程序员(几乎)以任何想要的方式扩展预先存在的类,无论这些类是你编写的还是捆绑到 Ruby 语言本身中都没有关系。 在下面的示例中,我们将扩展 Integer 类以适合我们的需求。 在文本编辑器中键入程序并执行 ```rb # 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 ```rb 2018-10-10 10:27:37 +0530 ``` 该程序将当前时间与当前秒精确地相差 2 周,请注意,我们通过以下语句进行操作: ```rb puts Time.now + 2.week ``` `Time.now`获取当前的`Time`实例,并在其中添加`2.week`。 实际上,本机`Integer`类中没有名为`week`的方法,但是在程序中我们看到定义了`Integer`类,该类的方法名为`week`,当其调用时返回一周中的秒数 可以将其添加到时间对象或从中减去以获取过去和将来的时间。 你可以类似的方式在 Ruby 中扩展任何类,几乎可以重写任何方法。 当然,一些程序员将其视为威胁,因为一些意外更改可能会在你的代码中引入错误,但是,如果你真的喜欢 Ruby,则无关紧要。 ### 11.12。 反射 反射是计算机程序可以自行分析并随时进行修改的过程。 在接下来的页面中,我们将仅从头开始。 我们将在 irb 中尝试这些示例,因此在你的终端中键入 irb –simple-prompt。 在下面的 irb 提示中,我声明了一个字符串变量`a`并将其设置为值`“Some string”` ```rb >> a = "Some string" => "Some string" ``` 现在让我们看看可以使用变量`a`可用的方法。 为此,请在 irb 中键入`a.methods` ```rb >> 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", "<<"] ``` 如你所见,`a.methods`返回可以在`String`上使用的方法,即函数。 接下来,我们将尝试找出`a`属于什么类或类型。 我们当然知道它属于`String`类型,但是为了学习将`a.class`类型编程为 irb ```rb >> a.class => String ``` 并忠实地返回 a 属于`String`类型。 很好,我们已经看到了一些有关反射的东西。 为了更好地理解它,让我们定义我们自己的类,看看反射如何作用于它。 在文本编辑器中输入以下程序 [Reflection.rb](code/reflection.rb) 并执行它。 ```rb # 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 ```rb 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 ``` 如上所示,你必须具有相当大的输出。 现在让我们遍历代码。 首先,我们定义一个名为`Someclass`的类,其中有两个属性`a`和`b`。 我们有一个称为`private_method`的私有方法,一个名为`protected_method`的受保护方法以及名为`public_method`的公共方法。 定义完类后,我们创建一个类型为`Someclass`的名为`something`的变量,并在以下几行中为其属性赋值 ```rb something = Someclass.new something.a = 'a' something.b = 123 ``` 接下来,我们要求 ruby 解释器使用忠实执行的以下语句`puts "something belongs to #{something.class}"`打印变量`something`的类,因此得到以下输出: ```rb something belongs to Someclass ``` 接下来,我们想知道类型为`Someclass`的对象`something`是否具有任何实例变量。 要知道它,我们使用以下代码: ```rb puts "something has the following instance variables:" puts something.instance_variables.join(', ') ``` 为此,我们得到以下输出 ```rb something has the following instance variables: @a, @b ``` 接下来,我们想知道有什么方法可以使用。 要知道我们可以使用`methods`函数,因此我们编写以下代码: ```rb 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 都会返回相同的结果。 所以我们将跳过关于这些的讨论 ```rb puts "something has the following public methods:" puts something.public_methods.join(', ') ``` 陈述 接下来,我们想知道`Someclass`中的私有,公共和受保护方法是什么,由于`something`属于`Someclass`,因此可以使用`private_methods`函数获得私有方法,因此通过给出以下语句 ```rb puts "something has the following private methods:" puts something.private_methods.join(', ') ``` 我们可以在某些类中获得私有方法。 通过使用`protected_methods`函数也可以得到类似的受保护方法,由于我的懒惰,我将不再讨论。 ### 11.13。 封装形式 你可能在生活中的某个时间点服用了胶囊片剂。 在其中,药物包装在明胶胶囊内。 当你用水服用时,它会滑到你的胃中,在那里水分解出明胶层,释放出其中的药物,从而治愈你的身体疾病。 如果你尝试在没有胶囊的情况下吞服药物,那将是一种痛苦的经历。 现代编程语言以类似的方式允许你隐藏不需要的细节,并让你的同伴程序员仅查看所需的细节。 这项技术称为封装,如果正确实施,则会产生清晰的代码,并且易于使用。 封装的另一个很好的例子是你的汽车。 在引擎盖下,你的汽车有成千上万个可以这种方式旋转的零件,但是你要做的就是打开钥匙并操作方向盘和踏板以获得驾驶体验。 你无需理会引擎盖内部发生的事情。 让我们看一个小例子,它将向我们解释封装的工作原理。 在文本编辑器中键入程序 [encapsulation.rb](code/encapsulation.rb) 并执行它。 ```rb # 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 ```rb First name: Ramanuja Last name: Iyengaar ``` 这样我们得到的人的名字叫 Ramanuja,姓氏叫 Iyengar。 由于以下陈述,将这两行打印出来 ```rb 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`方法中,请参见此代码 ```rb def name=(name) @firstname, @lastname = name.split end ``` 我们将其拆分,并使用以下代码将空格前的单词分配为`@firstname`,将空格后的单词分配为`@lastname`: ```rb @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。 多态性 Poly 表示很多,而 morphis 表示形式。 我认为它是希腊语还是拉丁语,谁在乎? 在编程语言中,你可以使用一件事情来做很多事情,下面举几个例子。 让我们采取谦虚的加号。 当我们取`“Hello ”`和`“World!”`并在它们之间加一个加号时,输出为`“Hello World!”`。 在技​​术交流中,我们称这种串联(连接在一起)。 这是 irb 示例: ```rb >> "Hello " + "World!" => "Hello World!" ``` 现在让我们在数字上使用此加号。 现在,我们将其停留在 134 和 97 之间。执行此操作时,答案为 231,而不是 13497。为什么? 这是因为加号被卡在不同事物之间时,训练它可以做不同的事情。 ```rb >> 134 + 97 => 231 ``` 当你将其插入字符串之间时,它们会连接在一起;当你将其插入数字之间时,则会添加它们。 因此,操作员会根据情况采取多种形式或进行不同的操作。 以类似的方式,如果将字符串乘以数字会发生什么。 好吧,如下所示 ```rb >> "Hello" * 5 => "HelloHelloHelloHelloHello" ``` 我们看到字符串被打印的次数。 因此,将“ Hello”乘以 5 可打印“ Hello”五次。 在下一个示例中,我们将值 6 分配给名为 hello 的变量,并将其乘以 5 ```rb >> hello = 6 => 6 >> hello * 5 => 30 ``` 由于`hello`是带有数字的变量,因此将其与数字相乘会得到一个数字。 因此,你甚至可以看到乘法运算符根据情况采用多种形式或不同的函数。 就像这样,一个警察在家时对家人友善,当他受到轰击时,他的举止却有所不同。 长度运算符/函数以类似的方式,当你找出字符串的长度时,它会告诉其中的字符数。 ```rb >> "some text".length => 9 ``` 当你找到数组的长度时,它会告诉该数组具有的元素数。 ```rb >> [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) ,其源代码是这样的 ```rb #!/usr/bin/ruby # class_constant.rb class Something Const = 25 end puts Something::Const ``` Output ```rb 25 ``` 注意在`Something`类中的代码段,你会看到`Const = 25`语句。 如果你读得很好,你可能会意识到 Ruby 中的常量以大写字母开头。 在类`Something`中,我们声明了一个常量名称`Const`并将其分配给 25。 请注意,语句`puts Something::Const`和`puts`用于打印抛出的几乎所有内容。 在这里,我们抛出`Something::Const`,它忠实地打印出恒定值。 因此,可以通过`&lt;class_name&gt;::&lt;constant_name&gt;`访问类常量,这就是访问类常量的方式。 让我们看看类实例如何访问类常量。 键入程序 class_constant_1.rb ```rb #!/usr/bin/ruby # class_constant.rb class Something Const = 25 end puts Something::Const ``` Output ```rb 25 class_constant_1.rb:10: undefined method `Const' for #<Something:0xb745eb58> (NoMethodError) ``` 因此,在上面的程序中,我们声明了一个类别为`Something`的变量`s`。 在`puts s.Const`行中,我们尝试通过其实例变量 s 访问某个内部的常量值,并且出现 No Method Error,或者 Ruby 解释器认为`Const`是一种方法,因为我们使用了`s.Const`。 要解决此问题,你可以编写一个名为`Const`的方法,然后按 [class_constant_2.rb](code/class_constant_2.rb) 中所示的方法进行调用 ```rb include::code/class_constant_2.rb ``` Output ```rb 25 25 ``` 因此,定义方法 &lt;sup class="footnote"&gt;[ [33](#_footnotedef_33 "View footnote.") ]&lt;/sup&gt; 并从中返回`Const`解决了该问题。 有人可能会认为可以使用实例变量通过使用双冒号(::)而不是 [class_constant_3.rb](code/class_constant_3.rb) 中所示的点运算符来访问类常量值,正如你从其输出中看到的那样,它将无法正常工作 ```rb #!/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 ```rb 25 class_constant_3.rb:14: #<Something:0xb74029fc> is not a class/module (TypeError) ``` ### 11.16。 功能别名 有时你可能已经编写了一个函数,并且可能希望将其重命名为某种东西,那时候该怎么办? 如果你在许多地方都使用过该功能,并且必须重命名,那么你必须在许多地方不断更改名称。 幸运的是,ruby 提供了一个名为`alias`的关键字,你可以使用该关键字为函数设置另一个名称。 看一下下面的程序,尤其是这一行`alias :shout :make_noise`。 ```rb # function_alias.rb class Something def make_noise puts "AAAAAAAAAAAAAAHHHHHHHHHHHHHH" end alias :shout :make_noise end Something.new.shout ``` Output ```rb AAAAAAAAAAAAAAHHHHHHHHHHHHHH ``` 如你所见,我们将其称为`Something.new.shout`,并且由于我们已将`make_noise`别名为`shout`,因此将调用`make_noise`。 ### 11.17。 匿名类 类可以没有名称,这些类称为匿名类。 查看下面的示例,键入并执行: ```rb # anonymous_class.rb person = Class.new do def say_hi 'Hi' end end.new puts person.say_hi puts person.class ``` Output ```rb Hi #<Class:0x0000000002696840> ``` 因此,让我们看看它是如何工作的。 首先,你有一个作为类实例的变量`person`,我们将其分配给`person = Person.new`而不是`person = Person.new`,如下所示。 也就是说,我们正在动态创建一个没有任何名称的新类。 因此它是匿名的。 ```rb person = Class.new do **(1)** ...... end.new **(2)** ``` | **1** | 创建一个匿名类。 | | **2** | 一个匿名类,在其`end`应该已经用`new`初始化之后 | 如果你可以从上面的代码中立即看到匿名类的定义,则必须使用`.new`立即对其进行初始化。 这也使匿名类仅坚持一个变量。 现在,我们将所需的内容填写到匿名类中,如下所示: ```rb 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; 。 由于该类没有名称,因此没有要打印的名称。 如果你在下面的 irb 中看到我编写的代码,我们可以看到调用`a.class`时会显示`A`,即类名,但在上述情况下,我们没有名称。 ```rb >> class A; end => nil >> a = A.new >> a.class => A ``` 我认为`#&lt;Class:0x0000000002696840&gt;`中的`0x…​.`代表类存储在内存中的地址位置。