Ruby 中的面向对象编程 II
在 Ruby 教程的这一部分中,我们将继续讨论 Ruby 中的面向对象编程。
我们从属性访问器开始。 我们将介绍类常量,类方法和运算符重载。 我们将定义多态,并展示如何在 Ruby 中使用它。 我们还将提到模块和异常。
Ruby 属性访问器
所有 Ruby 变量都是私有的。 只能通过方法访问它们。 这些方法通常称为设置器和获取器。 创建一个设置器和一个获取器方法是非常常见的任务。 因此,Ruby 具有方便的方法来创建两种类型的方法。 它们是attr_reader
,attr_writer
和attr_accessor
。
attr_reader
创建获取器方法。 attr_writer
方法为该设置器创建设置器方法和实例变量。 attr_accessor
方法同时创建获取器方法,设置器方法及其实例变量。
#!/usr/bin/ruby
class Car
attr_reader :name, :price
attr_writer :name, :price
def to_s
"#{@name}: #{@price}"
end
end
c1 = Car.new
c2 = Car.new
c1.name = "Porsche"
c1.price = 23500
c2.name = "Volkswagen"
c2.price = 9500
puts "The #{c1.name} costs #{c1.price}"
p c1
p c2
我们有汽车类。 在类的定义中,我们使用attr_reader
和attr_writer
为Car
类创建两个获取器和设置器方法。
attr_reader :name, :price
在这里,我们创建两个名为name
和price
的实例方法。 注意,attr_reader
将方法的符号作为参数。
attr_writer :name, :price
attr_writer
创建两个名为name
和price
的设置方法,以及两个实例变量@name
和@price
。
c1.name = "Porsche"
c1.price = 23500
在这种情况下,调用了两个设置器方法以用一些数据填充实例变量。
puts "The #{c1.name} costs #{c1.price}"
在这里,调用了两个获取器方法以从c1
对象的实例变量获取数据。
$ ./arw.rb
The Porsche costs 23500
Porsche: 23500
Volkswagen: 9500
这是示例的输出。
如前所述,attr_accessor
方法创建获取器,设置器方法及其实例变量。
#!/usr/bin/ruby
class Book
attr_accessor :title, :pages
end
b1 = Book.new
b1.title = "Hidden motives"
b1.pages = 255
p "The book #{b1.title} has #{b1.pages} pages"
我们有一个Book
类,其中attr_accessor
创建两对方法和两个实例变量。
class Book
attr_accessor :title, :pages
end
设置标题和页面方法以及@title
和@pages
实例变量的attr_accessor
方法。
b1 = Book.new
b1.title = "Hidden motives"
b1.pages = 255
创建一个Book
类的对象。 两种设置方法填充对象的实例变量。
p "The book #{b1.title} has #{b1.pages} pages"
在此代码行中,我们使用两种获取器方法来读取实例变量的值。
$ ./accessor.rb
"The book Hidden motives has 255 pages"
这是示例输出。
Ruby 类常量
Ruby 使您可以创建类常量。 这些常量不属于具体对象。 他们属于阶级。 按照约定,常量用大写字母表示。
#!/usr/bin/ruby
class MMath
PI = 3.141592
end
puts MMath::PI
我们有一个带有PI
常量的MMath
类。
PI = 3.141592
我们创建一个PI
常量。 请记住,Ruby 中的常量不是强制性的。
puts MMath::PI
我们使用::
运算符访问PI
常量。
$ ./classconstant.rb
3.141592
运行示例,我们看到此输出。
Ruby to_s
方法
每个对象都有一个to_s
方法。 它返回对象的字符串表示形式。 请注意,当puts
方法将对象作为参数时,将调用该对象的to_s
。
#!/usr/bin/ruby
class Being
def to_s
"This is Being class"
end
end
b = Being.new
puts b.to_s
puts b
我们有一个Beinging
类,其中我们重写了to_s
方法的默认实现。
def to_s
"This is Being class"
end
创建的每个类都从基Object
继承。 to_s
方法属于此类。 我们覆盖to_s
方法并创建一个新的实现。 我们提供了一个易于理解的对象描述。
b = Being.new
puts b.to_s
puts b
我们创建一个Beinging
类,并调用to_s
方法两次。 第一次是显式的,第二次是隐式的。
$ ./tostring.rb
This is Being class
This is Being class
这是我们运行示例时得到的。
运算符重载
运算符重载是指不同的运算符根据其参数具有不同的实现的情况。
在 Ruby 中,运算符和方法之间只有微小的区别。
#!/usr/bin/ruby
class Circle
attr_accessor :radius
def initialize r
@radius = r
end
def +(other)
Circle.new @radius + other.radius
end
def to_s
"Circle with radius: #{@radius}"
end
end
c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2
p c3
在示例中,我们有一个Circle
类。 我们在类中重载了+
运算符。 我们使用它来添加两个圆形对象。
def +(other)
Circle.new @radius + other.radius
end
我们定义一个带有+
名称的方法。 该方法将两个圆形对象的半径相加。
c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2
我们创建两个圆形对象。 在第三行中,我们添加这两个对象以创建一个新对象。
$ ./operatoroverloading.rb
Circle with radius: 11
将这两个圆形对象相加会创建第三个半径为 11 的对象。
Ruby 类方法
Ruby 方法可以分为类方法和实例方法。 类方法在类上调用。 不能在类的实例上调用它们。
类方法不能访问实例变量。
#!/usr/bin/ruby
class Circle
def initialize x
@r = x
end
def self.info
"This is a Circle class"
end
def area
@r * @r * 3.141592
end
end
p Circle.info
c = Circle.new 3
p c.area
上面的代码示例展示了Circle
类。 除了构造器方法外,它还具有一个类和一个实例方法。
def self.info
"This is a Circle class"
end
以self
关键字开头的方法是类方法。
def area
"Circle, radius: #{@r}"
end
实例方法不能以self
关键字开头。
p Circle.info
我们称为类方法。 注意,我们在类名上调用该方法。
c = Circle.new 3
p c.area
要调用实例方法,我们必须首先创建一个对象。 实例方法总是在对象上调用。 在我们的例子中,c
变量保存对象,然后在圆对象上调用area
方法。 我们利用点运算符。
$ ./classmethods.rb
"This is a Circle class"
28.274328
描述 Ruby 中类方法的代码示例的输出。
有三种方法可以在 Ruby 中创建类方法。
#!/usr/bin/ruby
class Wood
def self.info
"This is a Wood class"
end
end
class Brick
class << self
def info
"This is a Brick class"
end
end
end
class Rock
end
def Rock.info
"This is a Rock class"
end
p Wood.info
p Brick.info
p Rock.info
该示例包含三个类。 它们每个都有一个类方法。
def self.info
"This is a Wood class"
end
类方法可以以self
关键字开头。
class << self
def info
"This is a Brick class"
end
end
另一种方法是将方法定义放在class << self
构造之后。
def Rock.info
"This is a Rock class"
end
这是在 Ruby 中定义类方法的第三种方法。
$ ./classmethods2.rb
"This is a Wood class"
"This is a Brick class"
"This is a Rock class"
我们看到在Wood
,Brick
和Rock
类上调用所有三个类方法的输出。
在 Ruby 中创建实例方法的三种方法
Ruby 有三种创建实例方法的基本方法。 实例方法属于对象的实例。 使用点运算符在对象上调用它们。
#!/usr/bin/ruby
class Wood
def info
"This is a wood object"
end
end
wood = Wood.new
p wood.info
class Brick
attr_accessor :info
end
brick = Brick.new
brick.info = "This is a brick object"
p brick.info
class Rock
end
rock = Rock.new
def rock.info
"This is a rock object"
end
p rock.info
在示例中,我们从Wood
,Brick
和Rock
类创建三个实例对象。 每个对象都有一个定义的实例方法。
class Wood
def info
"This is a wood object"
end
end
wood = Wood.new
p wood.info
这可能是定义和调用实例方法的最常用方法。 info
方法在Wood
类中定义。 之后,创建对象,然后在对象实例上调用info
方法。
class Brick
attr_accessor :info
end
brick = Brick.new
brick.info = "This is a brick object"
p brick.info
另一种方法是使用属性访问器创建方法。 这是一种方便的方法,可以节省程序员的键入时间。 attr_accessor
创建两个方法,即获取器和设置器方法。它还创建一个实例变量来存储数据。 使用信息设置器方法,将创建砖对象,并将数据存储在@info
变量中。 最后,该消息由info
获取器方法读取。
class Rock
end
rock = Rock.new
def rock.info
"This is a rock object"
end
p rock.info
在第三种方法中,我们创建一个空的Rock
类。 该对象被实例化。 以后,将动态创建一个方法并将其放置到对象中。
$ ./threeways.rb
"This is a wood object"
"This is a brick object"
"This is a rock object"
示例输出。
Ruby 多态
多态是对不同的数据输入以不同方式使用运算符或函数的过程。 实际上,多态意味着如果类 B 从类 A 继承,则不必继承关于类 A 的所有内容; 它可以完成 A 类所做的某些事情。 (维基百科)
通常,多态是以不同形式出现的能力。 从技术上讲,它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。
请注意,在静态类型的语言(例如 C++ ,Java 或 C# )和动态类型的语言(例如 Python 或 Ruby)中,多态的定义有所不同。 在静态类型的语言中,当编译器在编译时或运行时确定方法定义时,这一点很重要。 在动态类型的语言中,我们专注于具有相同名称的方法执行不同操作的事实。
#!/usr/bin/ruby
class Animal
def make_noise
"Some noise"
end
def sleep
puts "#{self.class.name} is sleeping."
end
end
class Dog < Animal
def make_noise
'Woof!'
end
end
class Cat < Animal
def make_noise
'Meow!'
end
end
[Animal.new, Dog.new, Cat.new].each do |animal|
puts animal.make_noise
animal.sleep
end
我们有一个简单的继承层次结构。 有一个Animal
基类和两个后代,即Cat
和Dog
。 这三个类中的每一个都有其自己的make_noise
方法实现。 后代方法的实现替换了Animal
类中方法的定义。
class Dog < Animal
def make_noise
'Woof!'
end
end
Dog
类中的make_noise method
的实现替换了Animal
类中的make_noise
的实现。
[Animal.new, Dog.new, Cat.new].each do |animal|
puts animal.make_noise
animal.sleep
end
我们为每个类创建一个实例。 我们在对象上调用make_noise
和sleep
方法。
$ ./polymorhism.rb
Some noise
Animal is sleeping.
Woof!
Dog is sleeping.
Meow!
Cat is sleeping.
这是polymorhism.rb
脚本的输出。
Ruby 模块
Ruby Module
是方法,类和常量的集合。 模块与类相似,但有一些区别。 模块不能有实例,也不能是子类。
模块用于对相关的类进行分组,方法和常量可以放入单独的模块中。 这也防止了名称冲突,因为模块封装了它们所包含的对象。 在这方面,Ruby 模块类似于 C# 名称空间和 Java 包。
模块还支持在 Ruby 中使用 mixins。 mixin 是 Ruby 工具,用于创建多重继承。 如果一个类从一个以上的类继承功能,那么我们说的是多重继承。
#!/usr/bin/ruby
puts Math::PI
puts Math.sin 2
Ruby 具有内置的Math
模块。 它具有多种方法和一个常数。 我们使用::
运算符访问PI
常量。 和类中一样,方法由点运算符访问。
#!/usr/bin/ruby
include Math
puts PI
puts sin 2
如果我们在脚本中包含模块,则可以直接引用Math
对象,而无需使用Math
名称。 使用include
关键字将模块添加到脚本中。
$ ./modules.rb
3.141592653589793
0.9092974268256817
程序的输出。
在下面的示例中,我们展示了如何使用模块来组织代码。
#!/usr/bin/ruby
module Forest
class Rock ; end
class Tree ; end
class Animal ; end
end
module Town
class Pool ; end
class Cinema ; end
class Square ; end
class Animal ; end
end
p Forest::Tree.new
p Forest::Rock.new
p Town::Cinema.new
p Forest::Animal.new
p Town::Animal.new
Ruby 代码可以在语义上进行分组。 岩石和树木属于森林。 游泳池,电影院,广场属于一个城镇。 通过使用模块,我们的代码具有一定的顺序。 动物也可以在森林和城镇中。 在一个脚本中,我们不能定义两个动物类。 他们会发生冲突。 将它们放在不同的模块中,我们可以解决问题。
p Forest::Tree.new
p Forest::Rock.new
p Town::Cinema.new
我们正在创建属于森林和城镇的对象。 要访问模块中的对象,我们使用::运算符。
p Forest::Animal.new
p Town::Animal.new
将创建两个不同的动物对象。 Ruby 解释器可以在它们之间进行区分。 它通过模块名称来标识它们。
$ ./modules3.rb
#<Forest::Tree:0x97f35ec>
#<Forest::Rock:0x97f35b0>
#<Town::Cinema:0x97f3588>
#<Forest::Animal:0x97f3560>
#<Town::Animal:0x97f3538>
这是modules3.rb
程序的输出。
本节的最终代码示例将演示使用 Ruby 模块的多重继承。 在这种情况下,这些模块称为混合模块。
#!/usr/bin/ruby
module Device
def switch_on ; puts "on" end
def switch_off ; puts "off" end
end
module Volume
def volume_up ; puts "volume up" end
def vodule_down ; puts "volume down" end
end
module Pluggable
def plug_in ; puts "plug in" end
def plug_out ; puts "plug out" end
end
class CellPhone
include Device, Volume, Pluggable
def ring
puts "ringing"
end
end
cph = CellPhone.new
cph.switch_on
cph.volume_up
cph.ring
我们有三个模块和一个类。 模块代表一些功能。 可以打开和关闭设备。 许多对象可以共享此功能,包括电视,移动电话,计算机或冰箱。 我们没有为每个对象类创建这种被切换为开/关的功能,而是将其分离到一个模块中,如有必要,该模块可以包含在每个对象中。 这样,代码可以更好地组织和紧凑。
module Volume
def volume_up ; puts "volume up" end
def vodule_down ; puts "volume down" end
end
Volume
模块组织负责控制音量级别的方法。 如果设备需要这些方法,则仅将模块包含在其类中。
class CellPhone
include Device, Volume, Pluggable
def ring
puts "ringing"
end
end
手机使用include
方法添加所有三个模块。 模块的方法在CellPhone
类中混合。 并且可用于该类的实例。 CellPhone
类还具有特定于它的自己的ring
方法。
cph = CellPhone.new
cph.switch_on
cph.volume_up
cph.ring
创建一个CellPhone
对象,然后在该对象上调用三个方法。
$ ./mixins.rb
on
volume up
ringing
运行示例将给出此输出。
Ruby 异常
异常是表示偏离正常程序执行流程的对象。 引发,引发或引发异常。
在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为了防止这种情况的发生,我们应该预期并应对预期程序操作中的错误。 为此,我们可以使用异常处理。
异常是对象。 它们是内置Exception
类的后代。 异常对象携带有关异常的信息。 它的类型(异常的类名),可选的描述性字符串和可选的回溯信息。 程序可以是Exception
的子类,或更常见的是StandardError
的子类,以获取提供有关操作异常的其他信息的自定义Exception
对象。
#!/usr/bin/ruby
x = 35
y = 0
begin
z = x / y
puts z
rescue => e
puts e
p e
end
在上面的程序中,我们有意将数字除以零。 这会导致错误。
begin
z = x / y
puts z
可能失败的语句放置在begin
关键字之后。
rescue => e
puts e
p e
end
在rescue
关键字后面的代码中,我们处理了一个异常。 在这种情况下,我们将错误消息打印到控制台。 e
是发生错误时创建的异常对象。
$ ./zerodivision.rb
divided by 0
#<ZeroDivisionError: divided by 0>
在示例的输出中,我们看到了异常消息。 最后一行显示名为ZeroDivisionError
的异常对象。
程序员可以使用raise
关键字自己引发异常。
#!/usr/bin/ruby
age = 17
begin
if age < 18
raise "Person is a minor"
end
puts "Entry allowed"
rescue => e
puts e
p e
exit 1
end
18 岁以下的年轻人不允许进入俱乐部。 我们在 Ruby 脚本中模拟这种情况。
begin
if age < 18
raise "Person is a minor"
end
puts "Entry allowed"
如果此人是未成年人,则会引发异常情况。 如果raise
关键字没有特定的异常作为参数,则会引发RuntimeError
异常,将其消息设置为给定的字符串。 该代码未到达puts "Entry allowed"
行。 代码的执行被中断,并在救援块处继续执行。
rescue => e
puts e
p e
exit 1
end
在救援块中,我们输出错误消息和RuntimeError
对象的字符串表示形式。 我们还调用exit
方法来通知环境脚本执行错误结束。
$ ./raise_exception.rb
Person is a minor
#<RuntimeError: Person is a minor>
$ echo $?
1
未成年人未获准进入俱乐部。 bash $?
变量设置为脚本的退出错误。
Ruby 的ensure
子句创建一个始终执行的代码块,无论是否存在异常。
#!/usr/bin/ruby
begin
f = File.open("stones", "r")
while line = f.gets do
puts line
end
rescue => e
puts e
p e
ensure
f.close if f
end
在代码示例中,我们尝试打开并读取Stones
文件。 I/O 操作容易出错。 我们很容易会有异常。
ensure
f.close if f
end
在确保块中,我们关闭文件处理器。 我们检查处理器是否存在,因为它可能尚未创建。 分配的资源通常放置在确保块中。
如果需要,我们可以创建自己的自定义异常。 Ruby 中的自定义异常应继承自StandardError
类。
#!/usr/bin/ruby
class BigValueError < StandardError ; end
LIMIT = 333
x = 3_432_453
begin
if x > LIMIT
raise BigValueError, "Exceeded the maximum value"
end
puts "Script continues"
rescue => e
puts e
p e
exit 1
end
假设我们处于无法处理大量数字的情况。
class BigValueError < StandardError ; end
我们有一个BigValueError
类。 该类派生自内置的StandardError
类。
LIMIT = 333
超出此常数的数字在我们的程序中被视为big
。
if x > LIMIT
raise BigValueError, "Exceeded the maximum value"
end
如果该值大于限制,则抛出自定义异常。 我们给异常消息"Exceeded the maximum value"
。
$ ./custom_exception.rb
Exceeded the maximum value
#<BigValueError: Exceeded the maximum value>
运行程序。
在本章中,我们结束了关于 Ruby 语言的面向对象编程的讨论。