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