Ruby 中的 refine() 方法和 using() 方法

yufei       6 年, 1 月 前       1567

今天我们还是继续来学习 Ruby 的有关知识吧。这篇文章,我想聊聊 Ruby 中的 monkey-patchingrefine()using() 两个方法。

Ruby 中的 monkey-patching

monkey-patching ,中文名 「 猴子补丁 」。猴子补丁的作用就是在运行时动态的替换属性。

对于 Ruby 来说,因为类和模块可以重新打开。因此,对猴子补丁的需求几乎为零。

比如下面的代码,我们将一个哈希表转换为字符串,也就是调用 to_s() 方法

demo.rb

h = {"name":"简单教程,简单编程"}
p h.to_s

运行结果如下

[yufei@www.twle.cn java]$ ruby demo.rb
"{:name=>\"简单教程,简单编程\"}"

如果我们想要改变 to_s() 方法的结果,很简单,我们只要重新定义这个方法即可,就像下面这样

demo.rb

class Hash
  def to_s
    'hash'
  end
end

h = {"name":"简单教程,简单编程"}
p h.to_s

运行结果如下

[yufei@www.twle.cn java]$ ruby demo.rb
"hash"

在 Ruby 运行时改变属性是如此的容易,以至于猴子补丁都是可有可无的。

这种在运行时给程序打补丁的方法很有用。虽然更可取的方法是使用 里式替换原则

猴子补丁最大的好处是什么? 可以暂时性的避免程序崩溃!!!

这又是什么原理么?

猴子补丁可以在打补丁的类的所有实例之间共享,比如,在我们上面的范例中,就是所有的哈希表都共享替换后的 to_s() 方法。

要不,我们再举个例子??

这样吧,我就拿 Ruby On Rails 中常用到的将哈希表转换为 JSON 作为例子。

假设我们存在一个模块 lib/my_lib.rb ,这个模块中定义了一个类 MyLib ,这个类包含一个方法 print_config() 会调用类的哈希表的 to_json() 方法将配置转换为 json

lib/my_lib.rb

# lib/my_lib.rb

class MyLib
  attr_accessor :config

  def initialize
    config = {}

    yield(config) if block_given?
  end

  def print_config
    "config: #{config.to_json}" # <= Monkey-patch applied here
  end
end

那现在我们突然改变构架,不希望 print_config() 方法返回 json。那要怎么做呢?

当然了,最好的办法就是直接修改 print_config() 方法。

但如果我们某些原因改不了 print_config 怎么办呢? 这时候,猴子补丁就派上用场了。

我们可以使用猴子补丁给哈希表打一个补丁,动态改变 to_json() 方法,如下

lib/core_ext/hash.rb

class Hash
  # 临时猴子补丁
  # 禁止 MyLib#print_config 输出配置
  def to_json
    ''
  end
end

但是,这个猴子补丁也会有副作用,假设其它地方也使用了 to_json() 方法,那就不会有任何输出了。

app/controllers/products_controller.rb

class ProductsController < ApplicationController
  def index
    @products = {
      products: [
        # list of products
      ]
    }

    render json: @products # <= Side-effect of the monkey-patch
  end
end

这样,将直接导致 GET /products.json 路由可能不会获得任何输出。因为猴子补丁会改变补丁所在类的所有实例。

这个范例,也演示使用 Ruby 的类打开机制直接修补类的一个限制。

为了解决这个问题,在 Ruby 2.0 中,引入了 Refinement 的概念。

refine()using() 方法

refine()using() 方法都在 Module 中定义。

Module#refine() 方法允许我们为特定类注册一个猴子补丁 ( monkey-patch ) ,然后通过调用 Module#using() 方法随时应用它。

这两个方法和上面提到的类重新打开机制最大的不同,就是把猴子补丁的定义和应用拆开。使得只要在特定情况下调用 using() 应用即可。

因此,接下来,我们主要谈论 refinement 机制,而不会猴子补丁。

我们先来看一段代码

demo.rb

module TemporaryPatch
  refine Hash do
    def to_s
      ''
    end
  end
end

my_ebook = {
  ebook:   'Ruby Object Model',
  url:     'https://goo.gl/87b1bi',
  message: 'feel free to have a look to my new project ;-)'
}

p my_ebook.to_s # => "{\"ebook\":\"Ruby Object Model\", ...}"

using TemporaryPatch

lolcat = { lol: 'cat' }

p lolcat.to_s   # => ""
p my_ebook.to_s # => ""

运行上面这段代码,输出结果如下

[yufei@www.twle.cn java]$ ruby demo.rb
"{:ebook=>\"Ruby Object Model\", :url=>\"https://goo.gl/87b1bi\", :message=>\"feel free to have a look to my new project ;-)\"}"
""
""

上面这段代码中,我们先使用 refine() 方法重新定义 to_s() 方法。定义完了之后呢,它并不会立即应用到 哈希表的所有类中,而是要等到调用 using TemporaryPatch 方法的时候才会应用。

请注意,模块名称并不重要,但最好根据细化的上下文对其进行命名。

因为一旦调用 using TemporaryPatch 方法,那么该补丁就会立即生效,而且会影响之后所有的 to_s() 调用。所以我们看到 p my_ebook.to_s 的输出结果也是空字符串。

注意,因为这种猴子补丁一旦应用,会影响所有实例,因此,总是在调用使用之前已实例化了 my_ebook 哈希,仍然可以看到它的输出结果为 ""

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.