# 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 描述的第一部分。