Ruby 的模块

我们在之前的章节中介绍了类,在本章节中,我会来介绍一下 Ruby 模块的概念以及如何去使用一个模块。

1. 什么是模块?

在 Ruby 中,模块在某种程度上类似于类:它们可以持有方法,就像类一样。但是,和类不同的是无法实例化模块,即模块不可以创建对象。因此,与类不同,模块没有new方法。

那么哪里需要使用模块呢?

使用模块,您可以在类之间共享方法:模块可以包含在类中,这使得它们的方法可以在多个类中使用,就像我们将这些方法复制并粘贴到类定义上一样。这种使用方式我们也称为Mixin

2. 模块的使用

2.1 创建模块

创建模块的时候我们会用到关键字module

实例:

module Encryption
end

现在我们就创建了一个什么方法都没有的Encryption模块。

2.2 添加方法

与类中定义实例方法一样,在模块中创建方法只需在模块中定义一个方法即可:

require 'digest'

module Encryption
  def encrypt(string)
    Digest::SHA2.hexdigest(string)
  end
end

现在模块拥有了一个名为encrypt的方法。这个方法是用于将传入字符串进行 SHA256 加密。

注意事项:因为和类不同,模块没有new方法,不能实例化成为一个对象,所以不能模块一般通过被类进行引用来执行相应的方法。

Encryption.new

# ---- 输出结果 ----
undefined method `new' for Encryption:Module (NoMethodError)

不过,模块可以通过模块名.方法名()的形式调用模块方法

实例:

require 'digest'

module Encryption
  def self.encrypt(string) # 注意这里多了一个self
    Digest::SHA2.hexdigest(string)
  end
end

puts Encryption.encrypt('super password')

# ---- 输出结果 ----
'02f10a4b97a846ae06d64073bb56469d8516bbe19bd1487e9d80ae7e9ec0ac1b'

模块方法的定义形式和类完全一样,因为模块就是类(Class)的父类(Superclass)(在 类的实质 章节中会详细讲解)。

class Person
end

p Person.class.superclass

# ---- 输出结果 ----
Module

2.3 通过引用模块重构代码

让我们用Person类举例:

require 'digest'

class Person
  
  def initialize(name)
    @name = name
  end

  def name
    @name
  end
  
  def password=password
    @password = password
  end

  def encrypted_password
    Digest::SHA2.hexdigest(@password)
  end
end

person = Person.new("Andrew")
person.password = "super password"
p person.encrypted_password


# ---- 输出结果 ----
'02f10a4b97a846ae06d64073bb56469d8516bbe19bd1487e9d80ae7e9ec0ac1b'

Person类拥有一个对密码加密的方法,让我们对这个方法进行重构。

我们要重构这个获取对密码进行加密之后的结果的方法encrypted_password。在这时我们选择引用Encryption模块来添加对字符串加密的encrypt方法。

实例:

require 'digest'

module Encryption
  def encrypt(string)
    Digest::SHA2.hexdigest(string)
  end
end

class Person
  include Encryption
  
  def initialize(name)
    @name = name
  end

  def name
    @name
  end
  
  def password=password
    @password = password
  end

  def encrypted_password
    encrypt(@password)
  end
end

person = Person.new("Andrew")
person.password = "super password"
p person.encrypted_password


# ---- 输出结果 ----
'02f10a4b97a846ae06d64073bb56469d8516bbe19bd1487e9d80ae7e9ec0ac1b'

解释: 在这里我们使用了include关键字来引用模块(引入模块一共有三种方式:includeextendprepend,在之后的章节中我们会对这三种情况逐个分析),引用的方法都会变成Person类的实例方法。重构后,我们调用encrypted_password时加密时使用的encrypt方法来自Encryption模块内,这样避免了在很多类中做同一种加密,每修改一次加密形式就要修改每一个类代码的问题。

上述这种情况假设我们还有其它需要加密内容的类,我们还希望将加密的方法保留在一个地方,这么做有 4 个好处:

  • 当我们想切换到另一种加密方式的时候,只需要更改这个模块的加密代码即可;

  • 我们不希望相同的加密逻辑代码在某些需要的位置重复被使用;

  • 可以把这种代码视为一个杂物,隐藏在另一个文件中,我们只需要关心类的工作,不需要关心加密事务的具体逻辑;

  • 使用模块来封装代码也会使可读性更高。

3. 小结

本章节中我们学习了模块的概念,区分了类与模块,模块不能创建实例,没有new方法,以及如何创建一个模块,向模块中添加方法、创建类似类方法的模块方法、引用模块,引用模块封装代码的好处。