14 Ruby 的类
我们在之前的章节讲了 Ruby 的很多对象,学会了如何使用简单的对象(例如:数字和字符串)以及数据结构数组和哈希来完成一些工作,了解如何使用方法,做好了充足的准备。本章中,我会为大家讲解 Ruby 的类,如何创建一个类以及类的实例,以及类的实例方法如何创建。
1. 什么是 Ruby 的类
当 Ruby 运行程序的时候,会创建一个空间,我们使用具体的事物对这些空间进行填充,我们可以调用这些事物的方法去做某些事情。同时,每个具体的事物(对象)都是一般思想或类型的实例,这些思想称为类。
在终端中我们可以通过class
方法来查看对象所属的类。
实例:
"Hello World".class
# ---- 输出结果 ----
String
对象是类的具体实例(表现)。
我们还可以使用is_a?
的方法来具体询问对象是否属于某个类:
实例:
"Hello World".is_a?(String)
# ---- 输出结果 ----
true
类是用于对象的蓝图。类具有很多特性,每个类定义了许多方法,这些方法特定用于此类事物(例如:字符串),每次从类创建对象的时候,每个对象都会拥有类给他们的这些方法,也可以说,对象从类继承了方法。
2. 一步一步创建类
2.1 定义一个类
首先让我们创建一个Calculator
的类,并逐步为它添加方法。
实例:
class Calculator
end
解释:我们使用关键字class
,名称和关键字end
定义一个类。
注意事项:我们定义类的时候,首字母一定要大写开头,否则会出现报错。同样对于由几个单词命名的类,我们应该使用大写字母分隔这些单词,例如:RubyStudyGroup
,而对于变量名和方法名,我们要使用下划线,且所有内容都应该小写,例如:local_variable
,method_name
。
2.2 创建一个实例
因为我们定义了一个完整的有效类,所以我们可以创建一个Calculator
实例,下面是创建一个Calculator
实例的方法。
Calculator.new
# ---- 输出结果 ----
#<Calculator:0x00007fb4132c0af0>
解释:new
方法是在类Calculator
上定义的(Calculator
它本身既是一个类也是一个对象,还记得我们说Ruby中所有东西都是对象吗。所以它也可以拥有方法)。此方法创建了一个新的实例,并返回它。
格式#<...>
告诉您此对象不是简单的东西,比如数字,字符串或数组。它只是告诉您类的名称,Calculator
以及Ruby分配给该对象的内部ID。
每个对象都有自己唯一的内部对象ID,当我在计算机上运行此代码时,Ruby分配的ID为0x00007fb4132c0af0
。如果您运行它,将会得到不同的结果。实际上,在大多数情况下,您可以忽略此ID。另外,我们可以检查我们的新计算器实例确实是类Calculator
的实例。
实例:
class Calculator
end
calculator = Calculator.new
puts calculator.class
puts calculator.is_a?(Calculator)
# ---- 输出结果 ----
Calculator
true
2.3 定义实例方法
我们可以在类和对象上定义和调用方法。类上可用的方法称为类方法,实例上可用的方法称为实例方法。刚才我们定义了一个类,现在让我们为它增加一个sum
方法。
实例:
class Calculator
def sum(number, other)
number + other
end
end
解释:我们在类中定义一个方法,即为这个类的实例方法。
注意事项:我们在定义实例方法的时候记得要缩进 2 个空格,表明sum
属于Calculator
类。这是一种规范。
那么我们如何使用这个定义的sum
实例方法呢?
我们可以实例化一个Calculator
,然后调用这个方法。
实例:
calculator = Calculator.new
puts calculator.sum(2, 3)
# ---- 输出结果 ----
5
2.4 初始化对象
在我们向类添加任何行为(方法)之前,我们希望能够为其提供一些初始数据。
让我们重新定义一个Person
类。
实例:
class Person
end
在我们的情况下,我们希望该人知道自己的名字。
实例:
class Person
def initialize(name)
end
end
解释:您会看到我们为这个类增加了一个名为initialize
的方法,并且可以接收一个name
参数。当类方法new
创建对象时,将在内部调用特殊的initialize
方法。
我们可以通过以下这种方式将名称传递给类的内部:
p Person.new("Andrew")
# ---- 输出结果 ----
#<Person:0x00007fb41326b118>
2.5 使用实例变量记录初始化属性
继续我们刚才做的事,我们想在实例化对象的时候,让对象保留自己初始化的名称,这时我们用到了实例变量(instance variable)
实例:
class Person
def initialize(name)
@name = name
end
end
解释:我们将name
的值赋予了实例变量@name
,它的作用域为整个在对象范围内的任何位置。
此时我们再次实例化Person
类:
p Person.new("Andrew")
#---- 输出结果 ----
#<Person:0x00007fb41321add0 @name="Andrew">
此时,我们创建的Person
对象中有了一个实例变量@name
,它的值为Andrew
。
2.6 属性读取器(getter)
我们已经创建了一个名为 Andrew 的Person
对象,那么如何获取它的名字呢。
实例:
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
此时我们可以通过向对象发送name
的消息,获取对应的信息。
实例:
person = Person.new("Andrew")
person.name
#---- 输出结果 ----
"Andrew"
解释:我们定义了一个方法name
,它返回了实例变量@name
,由此创建了一个属性读取器。属性读取器返回实例变量的值,也可以说,属性读取器公开了实例变量,让所有的人都可以读取它。
除此之外我们还有一种简单的写法,实现@name
的读取:
class Person
attr_reader :name
def initialize(name)
@name = name
end
end
和上面的操作是等效的。
2.7 属性设置器(setter)
现在我们添加一个功能,一个人不仅要有名称,也要能设置密码,这个密码我们希望在Person
对象被实例后才被告知,让我们进一步改变这个类。
class Person
def initialize(name)
@name = name
end
def name
@name
end
def password=(password)
@password = password
end
end
解释:如您所见,方法password =
只能执行一个参数(称为password
),并将此局部变量的值分配给实例变量@password
,其他什么也不做。
现在让我们为一个Person
对象添加密码。
实例:
person = Person.new("Andrew")
person.password=("super password")
p person
#<Person:0x00007fb413154810 @name="Andrew", @password="super password">
解释:在幕后,Ruby 在运行代码时将person.password ="something"
行转换为person.password =("something")
,这仅调用方法password=
,在右侧传递的值作为参数,这只是另一种方法。
同样我们也有一种简写方法给属性设置器:
class Person
attr_writer :password
def initialize(name)
@name = name
end
def name
@name
end
end
2.8 对象作用域
您可以通过对象上的任何方法访问:
所有局部变量;
所有实例变量;
所有对象的方法。
让我们写一个greet
方法来为您展示。
实例:
class Person
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + other_name + "! My name is " + @name + "."
end
end
boy = Person.new("Andrew")
girl = Person.new("Alice")
boy.greet(girl)
#---- 输出结果 ----
Hi Alice! My name is Andrew.
解释:这是一个对象交互的例子,我们定义了一个greet
方法,这个里面调用了@name
实例变量,other
是一个参数,代表Person
的对象。
注意事项:当 Ruby 找到一个标识符时,Ruby 首先寻找一个局部变量,然后寻找一个方法。上述代码name
没有调用实例方法name
而是取的other.name
的值就是因为这个原因。
2.9 self
每个对象都通过调用 self 的方式以每种方法认识自己。这是 Ruby 中的一个特殊关键字,它的意思是对象本身。
class Person
def initialize(name)
@name = name
p self
end
end
person = Person.new("Anja")
p person
#---- 输出结果 ----
#<Person:0x007f9994972428 @name="Anja">
#<Person:0x007f9994972428 @name="Anja">
解释:如您所见,我们两次输出相同的对象。一次在初始化方法中使用p self
,一次在外部范围中使用p person
。您还可以看到,这两个实例的神秘对象 ID 相同。因此我们可以知道它确实是同一对象。
所以,之前的方法我们可以这样修改:
class Person
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + name + "! My name is " + self.name + "."
end
end
boy = Person.new("Andrew")
girl = Person.new("Alice")
boy.greet(girl)
#---- 输出结果 ----
Hi Alice! My name is Andrew.
解释:现在,我们再次在两个不同的对象上调用方法name
。当 Ruby 看到self
时,它知道我们正在引用当前的Person
对象,并在其上调用方法name
。
注意事项:self
是一个关键字并不是一个方法,我们从下面的代码可以证明。
person = Person.new("Andrew")
p person.self
#---- 输出结果 ----
NoMethodError (undefined method `self' for #<Person:0x00007fb413290bc0 @name="Andrew">)
关键字是在 Ruby 中具有特殊含义的单词,例如class
,def
,end
和self
。
2.10 类方法(Class Method)
类方法的格式为:类名.方法名()
,通俗来说就是类名调用的方法,在其他语言中也称为静态方法(Static Method)。
现在我们要输出Person
类英语和中文名称,对Person
类要这样修改。
实例:
class Person
def self.cn_name
'人'
end
def self.en_name
'Person'
end
end
puts Person.cn_name
puts Person.en_name
#---- 输出结果 ----
人
Person
解释: 上述我们定义了两个类方法,一个为cn_name
,一个为en_name
,我们使用Person.cn_name
和Person.en_name
来调用这两个方法。
除此之外,我们还可以使用另外一种写法:
class Person
class << self
def cn_name
'人'
end
def en_name
'Person'
end
end
end
puts Person.cn_name
puts Person.en_name
解释:这种写法在单件类章节中会详细讲解,可以将它看成在一个类中定义多个类方法的一种便捷办法。
假如我们要在类的外面定义类方法还可以这样写:
class Person
end
def Person.cn_name
'人'
end
def Person.en_name
'Person'
end
puts Person.cn_name
puts Person.en_name
#---- 输出结果 ----
人
Person
Tips:创建了一个类的时候,类方法和类的作用域内(非实例方法部分),self表示的是类本身。
实例:
class Person
puts "in class: #{self}"
puts self == Person
def self.cn_name
puts "in class method: #{self}"
puts self == Person
end
end
#---- 输出结果 ----
in class: Person
true
in class method: Person
true
3. 小结
本章中我们学习了如何创建一个 Ruby 的类,如何定义一个类、创建一个实例、定义实例方法、初始化对象、使用实例变量记录、初始化属性、属性读取器、属性设置器、对象作用域以及了解了 self 的含义。