ZetCode

Ruby prepend 方法

最后修改日期:2025 年 4 月 27 日

本教程解释了如何使用 Ruby 的 prepend 方法。这个强大的模块包含方法会改变类中的方法查找顺序。

prepend 方法将一个模块插入到类的祖先链之前。这意味着模块的方法会覆盖同名的类方法。它与 include 相反。

Prepending 对于方法覆盖、装饰和面向切面编程非常有用。它可以在修改行为的同时实现关注点的清晰分离。多个 prepend 调用以相反的顺序堆叠。

基本 prepend 示例

这个简单的例子演示了 prepend 如何改变方法查找顺序。模块方法优先于类方法。

basic_prepend.rb
module Greeter
  def greet
    "Hello from module!"
  end
end

class Person
  prepend Greeter
  
  def greet
    "Hello from class!"
  end
end

puts Person.new.greet

输出显示调用的是模块的方法,而不是类的方法。这是因为 prepend 将 Greeter 放在了 Person 祖先链的前面。

多次 prepend 调用

当包含多个模块时,它们会以相反的顺序插入。最后 prepend 的模块出现在祖先链中的第一个。

multiple_prepends.rb
module A
  def greet
    "A says: #{super}"
  end
end

module B
  def greet
    "B says: #{super}"
  end
end

class Person
  prepend A
  prepend B
  
  def greet
    "Hello!"
  end
end

puts Person.new.greet
puts Person.ancestors

输出显示 B 的方法调用 A 的方法,A 的方法调用原始方法。祖先列表显示 B 在 A 之前,A 在 Person 之前。

prepend 与 include 对比

此示例对比了 prepend 和 include。Include 将模块放在类之后,在祖先链中,而 prepend 将它们放在类之前。

prepend_vs_include.rb
module M
  def greet
    "Module says: #{super}"
  end
end

class Included
  include M
  
  def greet
    "Class says hello!"
  end
end

class Prepended
  prepend M
  
  def greet
    "Class says hello!"
  end
end

puts "Include: #{Included.new.greet}"
puts "Prepend: #{Prepended.new.greet}"
puts "Included ancestors: #{Included.ancestors}"
puts "Prepended ancestors: #{Prepended.ancestors}"

使用 include 时,类方法获胜。使用 prepend 时,模块方法获胜。祖先输出显示了不同的方法查找顺序。

在 prepend 中使用 super

super 关键字可以自然地与 prepend 配合使用,从而实现链式方法调用。这使得强大的方法装饰模式成为可能。

super_with_prepend.rb
module Logging
  def save
    puts "Logging before save"
    super
    puts "Logging after save"
  end
end

class Document
  prepend Logging
  
  def save
    puts "Saving document..."
  end
end

Document.new.save

Logging 模块包装了原始的 save 方法,在之前和之后添加了行为。这是用于跨关注点的常见 prepend 用例。

prepend 用于方法覆盖

prepend 可以覆盖父类的方法,而不仅仅是当前类的方法。此示例显示了覆盖继承的方法。

override_inherited.rb
class Animal
  def speak
    "Animal sound"
  end
end

module Loud
  def speak
    "#{super.upcase}!!!"
  end
end

class Dog < Animal
  prepend Loud
  
  def speak
    "Woof"
  end
end

puts Dog.new.speak
puts Dog.ancestors

Loud 模块覆盖了 Dog 的 speak 和 Animal 的 speak。输出显示模块的版本首先被调用,然后可以调用 super。

动态 prepend

prepend 可以在运行时调用,而不仅仅是在类定义期间。这允许动态地修改行为。

dynamic_prepend.rb
module AdminFeatures
  def access
    "Admin access granted"
  end
end

class User
  def access
    "Regular user access"
  end
end

user = User.new
puts user.access

User.prepend(AdminFeatures)
puts user.access

在 prepend 了 AdminFeatures 之后,即使是现有实例也能获得新行为。这展示了 Ruby 的动态特性和 prepend 的运行时效果。

prepend 在实际中的应用

此示例展示了 prepend 的实际用例 - 在不修改原始类的情况下为耗时的计算添加缓存。

caching_example.rb
module Cache
  def calculate
    @cache ||= {}
    @cache[inputs] ||= super
  end
  
  def inputs
    [@x, @y]
  end
end

class Calculator
  prepend Cache
  
  def initialize(x, y)
    @x = x
    @y = y
  end
  
  def calculate
    puts "Performing expensive calculation..."
    @x * @y
  end
end

calc = Calculator.new(5, 10)
puts calc.calculate
puts calc.calculate

Cache 模块会透明地添加记忆化。第二次调用 calculate 会返回缓存的结果,而无需重新计算。原始的 Calculator 保持不变。

来源

Ruby Module#prepend 文档

本教程介绍了 Ruby 的 prepend 方法,并通过实际示例展示了方法覆盖、装饰和动态行为修改。

作者

我叫 Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面有十多年的经验。

列出 所有 Ruby 教程