【问题标题】:In Ruby, what are the use cases for adding methods to an instance's singleton class?在 Ruby 中,向实例的单例类添加方法的用例是什么?
【发布时间】:2014-12-03 14:30:45
【问题描述】:

感谢其他一些帖子和阅读,我了解单例/元类。我理解我们为什么要在课堂上使用它们。但我仍然不明白为什么我们要在实例对象上使用它们。而且我还没有在实践中看到它。

我指的是这样的东西:

class Vehicle
    def odometer_reading
        # some code
    end
end

my_car = Vehicle.new

def my_car.open_door
    # some code
end

乍一看,这似乎是个坏主意,因为它会导致难以理解代码和调试。

我们为什么要这样做?有哪些例子说明这是个好主意?

【问题讨论】:

    标签: ruby methods singleton instance metaclass


    【解决方案1】:

    一个例子是将它用于测试目的:创建模拟对象和双重对象,存根方法。调试就在附近:为您怀疑行为不端的特定对象重新定义日志记录方法,以便在调试会话期间将日志信息直接打印到控制台(或打印更多信息)。

    另一个例子是处理特殊情况——你可以这样做而不是继承。从一个经典的例子开始,如果你使用两种类型的Employees,比如Engineers和SalesPersons,补偿计算的规则不同,你可以把通用的逻辑放到Employee类中,然后从中继承其他两个类并在那里实现自己的calculate_salary 方法。现在,如果有一个异常值——一个你已经同意不同薪酬计划的明星推销员,一个有非常特殊计划的 CEO,等等——而不是为这个特殊员工创建一个完整的子类,你可以定义这个代表该员工的特定对象的方法。

    第三个示例是处理对象生命周期和性能注意事项。而不是在某些处理方法中具有各种状态的长 case。例如。对于在后台透明地缓存整个文件的文件读取类(我知道一种对现实生活来说过于简单的方法,但只是作为一个模型),当文件没有被完全读取时,所有读取请求都应该检查是否请求的数据已经在缓存中或应该从磁盘读取。一旦文件被完全读取,它们总是从缓存中删除。一旦文件被完全读取到缓存中,您可以简单地在对象级别重新定义 read 方法,而不是使用 if(如果有更多状态,则使用 case)来处理。对于这个简单的示例,它不会带来任何可观的性能优势(如果有任何好处的话),但对于更复杂的情况可能是值得的。

    【讨论】:

    • 感谢您提供的具体示例。如果我们这样做,编程社区是否认为这是一种好的做法,更不用说 Ruby 社区了?如您所知,我很难理解这是个好主意。
    • 是的,对我来说似乎是一个很好的做法 - 非常 Ruby 方式。一些(非常快速的)谷歌搜索后的几个链接:blog.flatironschool.com/post/67061115355/… - 他们认为这很好(并且无论如何都在后台使用了类方法)。这篇文章 (bettong.net/2011/11/07/notes-on-eloquent-ruby-number-3) 引用了“Eloquent Ruby”一书建议的这种方法。现在,我手头没有这本书,但是这本书几乎是经典的(嗯,对我来说是肯定的),作者说它很好,是正确的“Ruby 方式”的一个例子,那么它可能就是。
    【解决方案2】:

    您不会使用def 添加它们,这是一种相当严格的方法,而是使用define_methodextend 之类的东西。

    虽然这不是你经常做的事情,但这确实意味着你可以做一些相当不寻常的事情。 Rails 中的 ActiveRecord 以数组的形式生成结果,并添加了其他方法来执行其他操作。

    对象-关系映射器是您可能想要执行此操作的情况。有时,根据您获取记录的方式,可用的方法会有很大不同。能够动态添加这些意味着每个获取的对象都可以完全自定义,即使它们具有相同的类和通用方法。

    【讨论】:

    • 我明白了。当你说“做一些相当不寻常的事情”时,这就是我的出发点。为什么我们想做不寻常的事情?我想在非常特定的情况下这样做相对安全,没问题。
    • 这是一个示例in another question,使用响应文档结构的元编程。
    【解决方案3】:

    另一个例子:你有一个哈希数组,你希望每个哈希都有一个方法调用 getter 和 setter。比如:

    user = HashOnSteroids.new(name: 'John')
    user[:name] # => 'John'
    user[:name] = 'Joe'
    user.name # => 'Joe'
    user.name = 'John'
    user.set(name: 'Jim', age: 5)
    

    这意味着您不能在类中编写标准方法定义,因为每个散列将有一组不同的键(方法名称)。这意味着您必须求助于定义单例方法,以便每个对象都有自己的一组方法(而不是一组共享方法)。

    警告:在这个用例中使用单例方法是非常低效的。偷偷摸摸的method_missing 更快,并且使用更少的内存,因为它不必分配十亿个 proc 对象。

    【讨论】:

    • OpenStruct 做了类似的事情。
    • OpenStruct 看起来很像 Javascript。
    • 我知道,但它会消耗大约 5 倍的内存 (MRI),而且似乎不支持批量设置。推出我们自己的解决方案实际上既简单又有趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-05
    • 2011-11-15
    • 2011-08-09
    • 2010-09-17
    • 1970-01-01
    • 2016-01-13
    • 1970-01-01
    相关资源
    最近更新 更多