Ruby 中的元编程
Ruby 编程语言以两大特点而闻名:一是其面向对象编程的核心理念,即“一切皆对象”;二是其定义 DSL 和“魔法”类的惊人灵活性。这种“魔法”是 Ruby 特有的,它被称为元编程。本文将深入探讨 Ruby 的深层嵌套细节,以及如何使用元编程创建神奇的 API!
目录
元编程到底是什么?
元编程的概念是,你用一个程序来编写另一个程序,这有点令人困惑,对吧?但相信我,它其实很简单,想象一下以下场景:
你创建一个函数,用它在另一个文件中写入特定功能,或者甚至在同一个文件中追加内容,包含某个类、对象或其他对象的完整实现。这就是代码创建更多代码,明白了吗?这就是元编程!
元编程的一个很酷的例子是 DSL(领域特定语言),它在同一语言中定义特定的语法来表达更复杂的概念,例如查询数据库或定义路由。Ruby 生态系统大量使用元编程并不是什么新鲜事,因此我们可以继续编写优雅、简洁且带有“魔力”感的代码。
在 Ruby 中一切都是对象,这是什么意思?
您可能看到过那句著名的话“在 Ruby 中,一切都是对象”,但这到底是什么意思,以及它与元编程有什么关系?
在 Ruby 中,所有事物都是对象,甚至核心数据结构(如数字、字符串等)也是如此,如下例所示:
irb(main):009> 5.class
=> Integer
irb(main):010> 5.class.class
=> Class
irb(main):011> ''.class
=> String
irb(main):012> ''.class.class
=> Class
irb(main):013> true.class
=> TrueClass
irb(main):014> true.class.class
=> Class
最终,即使是简单的结构也是从一个公共类对象定义的,并且可以拥有对象所知道的所有内容,例如方法、继承等......
这带来了各种疯狂的操作可能性,而元编程正是在这个特定领域蓬勃发展。由于万物皆对象,我们拥有了动态改变对象的能力。这使我们能够轻松地在语言中创建 DSL。
让我们尝试一起理解这个元编程的东西而不使用 ruby 提供的任何现代语法,好吗?
免责声明:以下示例不是如何实际实现元编程,它只是一种抽象,以便您可以理解该概念的一般思想。
想象一下,我们有一个带有函数的哈希,模拟一个带有方法的类对象,我们甚至可以通过访问“属性”并调用“方法来调用这个函数”。
my_obj = {
fn: ->() { puts 'original method' }
}
my_object[:fn].() # => original method
定义此初始对象后,可以编写一个接收此对象的函数并将另一个函数附加到它,如下所示:
my_obj = {
fn: ->() { puts 'original method' }
}
def add_new_method(obj, cb)
{another_fn: cb}.merge(obj)
end
并且此函数正常工作后,我们可以通过调用新创建的函数来向其添加新功能:
my_obj = {
fn: ->() { puts 'original method' }
}
def add_new_method(obj, key, cb)
{key => cb}.merge(obj)
end
my_obj[:fn].() # => original method
my_obj = add_new_method(my_obj, :another_fn, ->() { puts 'another method' })
my_obj[:another_fn].() # => another method
my_obj = add_new_method(my_obj, :hello, ->(target) { puts "hello #{target}" })
my_obj[:hello].("world") # => hello world
这是元编程的核心概念,当然,ruby 本身提供的现代功能可以让你做更多的事情,例如监听特定事件等。但围绕它的核心思维是操纵底层对象并为其添加新功能。
但是 Rails 呢?这个框架如何应用这个概念来最大限度地提升开发者体验
Ruby on Rails框架长期以来一直是最知名、最强大的 Ruby 框架,其核心理念是提供尽可能简洁的代码来实现应用程序的众多功能。为了提供这种程度的抽象和优雅的语法,Rails 大量依赖元编程,因此我们可以用更少的代码实现更多的功能。
validates
诸如、、、之has_many
类的方法只是 ORM 部分附带的几个方法,它们为声明性模型代码提供了各种好处,如以下示例所示:scope
before_save
class User < ApplicationRecord
has_many :posts
validates :username, presence: true
before_save :hash_password
scope :recent, -> { where('created_at > ?', 1.week.ago)}
private
def hash_password
self.password = hash(self.password)
end
end
has_many
使我们能够在模型本身内表达用户和帖子的数据库关系,在这个特定的例子中,Post
模型应该使用另一种称为的方法belongs_to
。validates
当我们尝试使用此特定模型执行任何创建方法时允许验证before_save
充当一个钩子,以符号的形式通知方法,在将记录保存到数据库之前将调用此特定方法。scope
提供一种使用特定查询在此模型上创建新方法的方法,这样我们就可以调用类似的方法User.recent
并获取函数的结果。
并且,框架的不同部分(例如控制器、视图、助手等)还有更多的元编程模式。这确实是使 Rails 成为受人喜爱的框架的重要组成部分。
如何动态定义方法
现在来说说更实际的部分,动态创建方法和 Ruby 中的其他方法一样简单优雅!希望我们使用的语言很成熟,对吧?👀 /j
最基本的动作就是动态定义一个方法,我们可以使用 ruby 提供的内置语法 called define_method
,下面我们来看一个实际的例子:
class FirstExample
define_method(:example_method) do
puts 'example method'
end
end
FirstExample.new.example_method # => example method
class SecondExample
def self.define_new_method(method_name, &body)
define_method(method_name, &body)
end
end
SecondExample.define_new_method(:hello) { |target| puts "Hello #{target}" }
SecondExample.new.hello("world") # => Hello world
该类FirstExample
展示了其简单用法的样子define_method
,它接受一个符号作为第一个参数和一个可以选择接收参数并执行任何操作的块(一旦实例化它将被替换为类方法),在示例的最后我们可以简单地实例化该类.new
并调用动态定义的方法。
这SecondExample
更复杂,需要定义一个方法来定义方法(最终是一些元语言引用)。虽然它只是在函数本身之上的一个抽象,但define_method
我们可以看到它的力量,它能够催生出一个真正的代码库,对吧?一个重要的细节是,我们接受第二个参数为,&body
因为这告诉 Ruby 以块的形式接受它,我们稍后会在方法示例中使用它hello
。
使用钩子检测类实例化的时刻
Ruby 不仅提供了动态创建新方法的方法,还提供钩子,因此我们可以在特定操作发生时运行任意代码(当然是围绕对象循环)。
当一个类被继承或包含时,甚至当一个类中不存在某个特定方法时,运行一些代码,这些事情非常酷而且功能强大,不是吗?
下面我们将详细了解不同钩子的一些可能性:
方法缺失
一旦在类中调用未知方法,此钩子函数就允许运行任意代码,下面是实现该钩子函数的类的示例:
class MethodMissingExample
def method_missing(name, *args)
puts "An unknown method was called using the name -> #{name} and the arguments -> #{args.inspect}"
end
end
MethodMissingExample.new.test_method(1, 2, 3) # => An unknown method was called using the name -> test_method and the arguments -> [1, 2, 3]
如您所见,只需创建一个具有名称的方法,method_missing
我们即可启用此功能,并可以使用该方法的name
和args
对其进行操作。如果您正在使用其他开发人员使用的公共代码,并且希望在方法拼写错误或确实未实现时提供自定义体验,这将非常有用。
包括
此方法仅适用于模块,当模块包含到另一个类或模块中时,将运行特定的块,如下所示:
module IncludedExample
def self.included(name)
puts "The #{name} was included!!"
end
end
module Test
include IncludedExample # => The Test was included!!
end
extend
类似地,我们有一个用于模块内的和操作的方法prepend
,它的工作方式与包含相同,下面通过一个示例进行展示:
module ExtendedExample
def self.extended(name)
puts "The #{name} was extended!!"
end
end
module PrependedExample
def self.prepended(name)
puts "The #{name} was prepended!!"
end
end
module Test
extend ExtendedExample # => The Test was extended!!
prepend PrependedExample # => The Test was prepended!!
end
遗传
回到具有类似功能的类,我们可以创建一个钩子,该钩子将在每次继承类时运行,如下所示:
class InheritedExample
def self.inherited(name)
puts "The #{name} class was inherited!!"
end
end
class Test < InheritedExample # => The Test class was inherited!!
end
结论
希望这篇文章对大家有所帮助!元编程是一门非常有趣的学习和使用方法,它可能会写出非常混乱的代码,但有了好的架构,就能创造出令人惊叹的体验,比如用 Ruby on Rails。如果有什么需要帮助的,尽管联系我,愿原力与你同在🍒
文章来源:https://dev.to/cherryramatis/metaprogramming-in-ruby-4p1g