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_variablemethod_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 " + 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 中具有特殊含义的单词,例如classdefendself

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_namePerson.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 的含义。