🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Ruby 中的面向对象编程 > 原文: [https://zetcode.com/lang/rubytutorial/oop/](https://zetcode.com/lang/rubytutorial/oop/) 在 Ruby 教程的这一部分中,我们将讨论 Ruby 中的面向对象编程。 编程语言具有过程编程,函数式编程和面向对象的编程范例。 Ruby 是一种具有某些函数式和过程特性的面向对象的语言。 面向对象编程(OOP)是一种使用对象及其相互作用设计应用和计算机程序的编程范例。 OOP 中的基本编程概念是: * 抽象 * 多态 * 封装 * 继承 抽象通过建模适合该问题的类来简化复杂的现实。 多态是将运算符或函数以不同方式用于不同数据输入的过程。 封装对其他对象隐藏了类的实现细节。 继承是一种使用已经定义的类形成新类的方法。 ## Ruby 对象 对象是 Ruby OOP 程序的基本构建块。 对象是数据和方法的组合。 在 OOP 程序中,我们创建对象。 这些对象通过方法进行通信。 每个对象都可以接收消息,发送消息和处理数据。 创建对象有两个步骤。 首先,我们定义一个类。 类是对象的模板。 它是一个蓝图,描述了类对象共享的状态和行为。 一个类可以用来创建许多对象。 在运行时从类创建的对象称为该特定类的实例。 ```ruby #!/usr/bin/ruby class Being end b = Being.new puts b ``` 在第一个示例中,我们创建一个简单的对象。 ```ruby class Being end ``` 这是一个简单的类定义。 模板的主体为空。 它没有任何数据或方法。 ```ruby b = Being.new ``` 我们创建`Being`类的新实例。 为此,我们有`new`方法。 `b`变量存储新创建的对象。 ```ruby puts b ``` 我们将对象打印到控制台以获取该对象的一些基本描述。 实际上,当我们打印对象时,我们将其称为`to_s`方法。 但是我们还没有定义任何方法。 这是因为创建的每个对象都继承自基本`Object`。 它具有一些基本功能,这些功能在所有创建的对象之间共享。 其中之一是`to_s`方法。 ```ruby $ ./simple.rb #<Being:0x9f3c290> ``` 我们得到对象类名。 ## Ruby 构造器 构造器是一种特殊的方法。 创建对象时会自动调用它。 构造器不返回值。 构造器的目的是初始化对象的状态。 Ruby 中的构造器称为`initialize`。 构造器不返回任何值。 使用`super`方法调用父对象的构造器。 它们按继承顺序被调用。 ```ruby #!/usr/bin/ruby class Being def initialize puts "Being is created" end end Being.new ``` 我们有一个存在类。 ```ruby class Being def initialize puts "Being is created" end end ``` `Betweening`类具有一个名为`initialize`的构造方法。 它将消息打印到控制台。 Ruby 方法的定义位于`def`和`end`关键字之间。 ```ruby Being.new ``` 创建`Being`类的实例。 创建对象时,将调用构造方法。 ```ruby $ ./constructor.rb Being is created ``` 这是程序的输出。 对象的属性是捆绑在该对象内部的数据项。 这些项目也称为实例变量或成员字段。 实例变量是在类中定义的变量,该类中的每个对象都有一个单独的副本。 在下一个示例中,我们初始化类的数据成员。 变量的初始化是构造器的典型工作。 ```ruby #!/usr/bin/ruby class Person def initialize name @name = name end def get_name @name end end p1 = Person.new "Jane" p2 = Person.new "Beky" puts p1.get_name puts p2.get_name ``` 在上面的 Ruby 代码中,我们有一个带有一个成员字段的`Person`类。 ```ruby class Person def initialize name @name = name end ``` 在`Person`类的构造器中,我们将一个`member`字段设置为一个值名称。 名称参数在创建时传递给构造器。 构造器是称为`initialize`的方法,该方法在创建实例对象时被调用。 `@name`是一个实例变量。 实例变量在 Ruby 中以`@`字符开头。 ```ruby def get_name @name end ``` `get_name`方法返回成员字段。 在 Ruby 中,成员字段只能通过方法访问。 ```ruby p1 = Person.new "Jane" p2 = Person.new "Beky" ``` 我们创建`Person`类的两个对象。 字符串参数传递给每个对象构造器。 名称存储在每个对象唯一的实例变量中。 ```ruby puts p1.get_name puts p2.get_name ``` 我们通过在每个对象上调用`get_name`来打印成员字段。 ```ruby $ ./person.rb Jane Beky ``` 我们看到了程序的输出。 `Person`类的每个实例都有其自己的名称成员字段。 我们可以创建对象而无需调用构造器。 Ruby 为此有一种特殊的`allocate`方法。 `allocate`方法为类的新对象分配空间,并且不会在新实例上调用`initialize`。 ```ruby #!/usr/bin/ruby class Being def initialize puts "Being created" end end b1 = Being.new b2 = Being.allocate puts b2 ``` 在示例中,我们创建两个对象。 第一个对象使用`new`方法,第二个对象使用`allocate`方法。 ```ruby b1 = Being.new ``` 在这里,我们使用`new`关键字创建对象的实例。 调用构造器方法`initialize`,并将消息打印到控制台。 ```ruby b2 = Being.allocate puts b2 ``` 在`allocate`方法的情况下,不调用构造器。 我们使用`puts`关键字调用`to_s`方法以显示该对象已创建。 ```ruby $ ./allocate.rb Being created #<Being:0x8ea0044> ``` 在这里,我们看到了程序的输出。 ## Ruby 构造器重载 构造器重载是在一个类中具有多种类型的构造器的能力。 这样,我们可以创建具有不同数量或不同类型参数的对象。 Ruby 没有我们从某些编程语言中知道的构造器重载。 可以使用 Ruby 中的默认参数值在某种程度上模拟此行为。 ```ruby #!/usr/bin/ruby class Person def initialize name="unknown", age=0 @name = name @age = age end def to_s "Name: #{@name}, Age: #{@age}" end end p1 = Person.new p2 = Person.new "unknown", 17 p3 = Person.new "Becky", 19 p4 = Person.new "Robert" p p1, p2, p3, p4 ``` 本示例说明了如何在具有两个成员字段的`Person`类上模拟构造器重载。 如果未指定`name`参数,则使用字符串`"unknown"`。 对于未指定的年龄,我们为 0。 ```ruby def initialize name="unknown", age=0 @name = name @age = age end ``` 构造器有两个参数。 它们具有默认值。 如果在创建对象时未指定自己的值,则使用默认值。 请注意,必须保留参数的顺序。 首先是名字,然后是年龄。 ```ruby p1 = Person.new p2 = Person.new "unknown", 17 p3 = Person.new "Becky", 19 p4 = Person.new "Robert" p p1, p2, p3, p4 ``` 我们创建四个对象。 构造器采用不同数量的参数。 ```ruby $ ./consover.rb Name: unknown, Age: 0 Name: unknown, Age: 17 Name: Becky, Age: 19 Name: Robert, Age: 0 ``` 这是示例的输出。 ## Ruby 方法 方法是在类主体内定义的函数。 它们用于通过对象的属性执行操作。 在 OOP 范式的封装概念中,方法至关重要。 例如,我们的`AccessDatabase`类中可能有一个`connect`方法。 我们无需告知该方法如何准确地连接到数据库。 我们只需要知道它用于连接数据库。 这对于划分编程中的职责至关重要,尤其是在大型应用中。 在 Ruby 中,只能通过方法访问数据。 ```ruby #!/usr/bin/ruby class Person def initialize name @name = name end def get_name @name end end per = Person.new "Jane" puts per.get_name puts per.send :get_name ``` 该示例显示了两种调用方法的基本方法。 ```ruby puts per.get_name ``` 常见的方法是在对象上使用点运算符,后跟方法名称。 ```ruby puts per.send :get_name ``` 替代方法是使用内置的`send`方法。 它以方法的符号作为参数。 方法通常对对象的数据执行某些操作。 ```ruby #!/usr/bin/ruby class Circle @@PI = 3.141592 def initialize @radius = 0 end def set_radius radius @radius = radius end def area @radius * @radius * @@PI end end c = Circle.new c.set_radius 5 puts c.area ``` 在代码示例中,我们有一个`Circle`类。 我们定义了两种方法。 ```ruby @@PI = 3.141592 ``` 我们在`Circle`类中定义了`@@PI`变量。 它是一个类变量。 类变量以 Ruby 中的`@@`信号开头。 类变量属于类,而不属于对象。 每个对象都可以访问其类变量。 我们使用`@@PI`计算圆的面积。 ```ruby def initialize @radius = 0 end ``` 我们只有一个成员字段。 它是圆的半径。 如果要从外部修改此变量,则必须使用公共可用的`set_radius`方法。 数据受到保护。 ```ruby def set_radius radius @radius = radius end ``` 这是`set_radius`方法。 它为`@radius`实例变量提供了一个新值。 ```ruby def area @radius * @radius * @@PI end ``` `area`方法返回圆的面积。 这是方法的典型任务。 它可以处理数据并为我们带来一些价值。 ```ruby c = Circle.new c.set_radius 5 puts c.area ``` 我们创建`Circle`类的实例,并通过在圆对象上调用`set_radius`方法来设置其半径。 我们使用点运算符来调用该方法。 ```ruby $ ./circle.rb 78.5398 ``` 运行示例,我们得到此输出。 ## Ruby 访问修饰符 访问修饰符设置方法和成员字段的可见性。 Ruby 具有三个访问修饰符:`public`,`protected`和`private`。 在 Ruby 中,所有数据成员都是私有的。 访问修饰符只能在方法上使用。 除非我们另有说明,否则 Ruby 方法是公共的。 可以从类的定义内部以及从类的外部访问`public`方法。 `protected`和`private`方法之间的区别很细微。 在类的定义之外都无法访问它们。 只能在类本身内部以及继承的或父类访问它们。 请注意,与其他面向对象的编程语言不同,继承在 Ruby 访问修饰符中不扮演的角色。 只有两件事很重要。 首先,如果我们在类定义的内部或外部调用方法。 其次,如果我们使用或不使用指向当前接收器的`self`关键字。 访问修饰符可防止意外修改数据。 它们使程序更强大。 某些方法的实现可能会发生变化。 这些方法是很好的私有方法。 公开给用户的接口仅应在确实必要时更改。 多年来,用户习惯于使用特定的方法,并且通常不赞成向后兼容。 ```ruby #!/usr/bin/ruby class Some def method1 puts "public method1 called" end public def method2 puts "public method2 called" end def method3 puts "public method3 called" method1 self.method1 end end s = Some.new s.method1 s.method2 s.method3 ``` 该示例说明了公共 Ruby 方法的用法。 ```ruby def method1 puts "public method1 called" end ``` 即使我们未指定`public`访问修饰符,`method1`也是公共的。 这是因为如果没有另外指定,默认情况下方法是公共的。 ```ruby public def method2 puts "public method2 called" end ... ``` `public`关键字后面的方法是公共的。 ```ruby def method3 puts "public method3 called" method1 self.method1 end ``` 从公共`method3`内部,我们调用带有和不带有`self`关键字的其他公共方法。 ```ruby s = Some.new s.method1 s.method2 s.method3 ``` 公共方法是唯一可以在类定义之外调用的方法,如下所示。 ```ruby $ ./public_methods.rb public method1 called public method2 called public method3 called public method1 called public method1 called ``` 运行示例,我们将获得以下输出。 下一个示例着眼于私有方法。 ```ruby #!/usr/bin/ruby class Some def initialize method1 # self.method1 end private def method1 puts "private method1 called" end end s = Some.new # s.method1 ``` 私有方法是 Ruby 中最严格的方法。 只能在类定义内调用它们,而不能使用`self`关键字。 ```ruby def initialize method1 # self.method1 end ``` 在方法的构造器中,我们称为私有`method1`。 带有`self`的方法被注释。 接收者无法指定私有方法。 ```ruby private def method1 puts "private method1 called" end ``` 关键字`private`之后的方法在 Ruby 中是私有的。 ```ruby s = Some.new # s.method1 ``` 我们创建`Some`类的实例。 禁止在类定义之外调用该方法。 如果我们取消注释该行,则 Ruby 解释器将给出错误。 ```ruby $ ./private_methods.rb private method called ``` 示例代码的输出。 最后,我们将使用受保护的方法。 Ruby 中的保护方法和私有方法之间的区别非常微妙。 受保护的方法就像私有的。 只有一个小差异。 可以使用指定的`self`关键字来调用它们。 ```ruby #!/usr/bin/ruby class Some def initialize method1 self.method1 end protected def method1 puts "protected method1 called" end end s = Some.new # s.method1 ``` 上面的示例显示了使用中的受保护方法。 ```ruby def initialize method1 self.method1 end ``` 可以使用`self`关键字来调用受保护的方法。 ```ruby protected def method1 puts "protected method1 called" end ``` 受保护的方法前面带有`protected`关键字。 ```ruby s = Some.new # s.method1 ``` 不能在类定义之外调用受保护的方法。 取消注释该行将导致错误。 ## Ruby 继承 继承是一种使用已经定义的类形成新类的方法。 新形成的类称为派生的类,我们派生的类称为基类。 继承的重要好处是代码重用和降低程序的复杂性。 派生类(后代)将覆盖或扩展基类(祖先)的功能。 ```ruby #!/usr/bin/ruby class Being def initialize puts "Being class created" end end class Human < Being def initialize super puts "Human class created" end end Being.new Human.new ``` 在此程序中,我们有两个类:基础`Being`类和派生的`Human`类。 派生类继承自基类。 ```ruby class Human < Being ``` 在 Ruby 中,我们使用`<`运算符创建继承关系。 `Human`类继承自`Being`类。 ```ruby def initialize super puts "Human class created" end ``` `super`方法调用父类的构造器。 ```ruby Being.new Human.new ``` 我们实例化`Being`和`Human`类。 ```ruby $ ./inheritance.rb Being class created Being class created Human class created ``` 首先创建`Being`类。 派生的`Human`类还调用其父级的构造器。 对象可能涉及复杂的关系。 一个对象可以有多个祖先。 Ruby 有一个方法`ancestors`,它提供了特定类的祖先列表。 每个 Ruby 对象自动是`Object`和`BasicObject`类以及`Kernel`模块的后代。 它们内置在 Ruby 语言的核心中。 ```ruby #!/usr/bin/ruby class Being end class Living < Being end class Mammal < Living end class Human < Mammal end p Human.ancestors ``` 在此示例中,我们有四个类:`Human`是`Mammal`,`Living`和`Being`。 ```ruby p Human.ancestors ``` 我们打印人类类的祖先。 ```ruby $ ./ancestors.rb [Human, Mammal, Living, Being, Object, Kernel, BasicObject] ``` 人类类具有三个习俗和三个内置祖先。 接下来是一个更复杂的示例。 ```ruby #!/usr/bin/ruby class Being @@count = 0 def initialize @@count += 1 puts "Being class created" end def show_count "There are #{@@count} beings" end end class Human < Being def initialize super puts "Human is created" end end class Animal < Being def initialize super puts "Animal is created" end end class Dog < Animal def initialize super puts "Dog is created" end end Human.new d = Dog.new puts d.show_count ``` 我们有四个类。 继承层次更加复杂。 `Human`和`Animal`类继承自`Being`类。 `Dog`类直接继承自`Animal`类,并且进一步继承自`Being`类。 我们还使用一个类变量来计算创建的生物的数量。 ```ruby @@count = 0 ``` 我们定义一个类变量。 类变量以`@@`信号开头,并且它属于该类,而不是该类的实例。 我们用它来计算创造的生物数量。 ```ruby def initialize @@count += 1 puts "Being class created" end ``` 每次实例化`Being`类时,我们都会将`@@count`变量加 1。 这样,我们就可以跟踪创建的实例数。 ```ruby class Animal < Being ... class Dog < Animal ... ``` `Animal`继承自`Being`,`Dog`继承自`Animal`。 另外,`Dog`也继承自`Being`。 ```ruby Human.new d = Dog.new puts d.show_count ``` 我们从`Human`和`Dog`类创建实例。 我们在`Dog`对象上调用`show_count`方法。 `Dog`类没有这种方法。 然后调用祖父级`Being`的方法。 ```ruby $ ./inheritance2.rb Being class created Human is created Being class created Animal is created Dog is created There are 2 beings ``` `Human`对象调用两个构造器。 `Dog`对象调用三个构造器。 有两个实例化的存在。 继承在方法和数据成员的可见性中不起作用。 与许多常见的面向对象的编程语言相比,这是一个明显的区别。 在 C# 或 Java 中,公共和受保护的数据成员和方法是继承的。 私有数据成员和方法不是。 与此相反,私有数据成员和方法也在 Ruby 中继承。 数据成员和方法的可见性不受 Ruby 中继承的影响。 ```ruby #!/usr/bin/ruby class Base def initialize @name = "Base" end private def private_method puts "private method called" end protected def protected_method puts "protected_method called" end public def get_name return @name end end class Derived < Base def public_method private_method protected_method end end d = Derived.new d.public_method puts d.get_name ``` 在示例中,我们有两个类。 `Derived`类继承自`Base`类。 它继承了所有三种方法和一个数据字段。 ```ruby def public_method private_method protected_method end ``` 在`Derived`类的`public_method`中,我们调用一种私有方法和一种受保护方法。 它们在父类中定义。 ```ruby d = Derived.new d.public_method puts d.get_name ``` 我们创建`Derived`类的实例。 我们称为`public_method`,也称为`get_name`,它返回私有`@name`变量。 请记住,所有实例变量在 Ruby 中都是私有的。 无论`@name`是私有的还是在父类中定义的,`get_name`方法都会返回该变量。 ```ruby $ ./inheritance3.rb private method called protected_method called Base ``` 该示例的输出确认,在 Ruby 语言中,子对象从其父级继承了`public`,`protected`,`private`方法和`private`成员字段。 ## Ruby `super`方法 `super`方法在父类的类中调用相同名称的方法。 如果该方法没有参数,它将自动传递其所有参数。 如果我们写`super()`,则不会将任何参数传递给父方法。 ```ruby #!/usr/bin/ruby class Base def show x=0, y=0 p "Base class, x: #{x}, y: #{y}" end end class Derived < Base def show x, y super super x super x, y super() end end d = Derived.new d.show 3, 3 ``` 在示例中,我们在层次结构中有两个类。 它们都有显示方法。 派生类中的`show`方法使用`super`方法调用基类中的`show`方法。 ```ruby def show x, y super super x super x, y super() end ``` 不带任何参数的`super`方法使用传递给`Derived`类的`show`方法的参数调用父级的`show`方法:此处,`x = 3`和`y = 3`。 `super()`方法不将任何参数传递给父级的`show`方法。 ```ruby $ ./super.rb "Base class, x: 3, y: 3" "Base class, x: 3, y: 0" "Base class, x: 3, y: 3" "Base class, x: 0, y: 0" ``` 这是示例的输出。 这是 Ruby 中 OOP 描述的第一部分。