【问题标题】:Is there a gem that provides support to detect changes to native ruby type instances?是否有一个 gem 支持检测对原生 ruby​​ 类型实例的更改?
【发布时间】:2019-03-31 18:19:51
【问题描述】:

虽然我同意扩展原生类型和对象是一种不好的做法,但不应该从它们继承。

在一个所谓的支持 gem(我找不到)中,使用本机类型的方式如下:

require 'cool-unkown-light-gem'

class MyTypedArray < CoolArray # would love to directly < Array
  def initialize(*args)
    super(*args)
    # some inits for DataArray
    @caches_init = false
  end

  def name?(name)
    init_caches unless !@caches_init
    !!@cache_by_name[name]
  end

  def element(name)
    init_caches unless !@caches_init
    @cache_by_name[name]
  end

  private

  # overrides the CoolArray method:
  # CoolArray methods that modify self will call this method
  def on_change
    @caches_init = false
    super
  end

  def init_caches
    return @cache_by_name if @caches_init
    @caches_init = true
    @cache_by_name = self.map do |elem|
      [elem.unique_name, elem]
    end.to_h
  end
end

修改 self 的子类未覆盖的父类的任何方法都将调用(在这种情况下)on_change 函数。这将允许不必重新定义这些方法中的每一个,以避免丢失对更改的跟踪。

假设MyTypedArray 将数组Foo 对象:

class Foo
  attr_reader :unique_name
  def initialize(name)
    @unique_name = name
  end
end

其使用的预期行为的简短示例:

my_array = MyTypedArray.new
my_array.push( Foo.new("bar") ).push( Foo.new("baz") )

my_array.element("bar").unique_name
# => "bar"

my_array.shift # a method that removes the first element from self
my_array.element("bar").unique_name
# => undefined method `unique_name' for nil:NilClass (NoMethodError)

my_array.name?("bar")
# => false

我知道我们应该搜索不可变类,但那些原生类型支持对同一对象进行更改,我们希望以适当的方式进行尽可能简短和容易的继承。

当然,我们非常欢迎任何想法、方法或建议。我不认为我是唯一对此有想法的人。

我之所以寻找维护的 gem,是因为不同的 ruby​​ 版本可能会为原生类型/类提供不同的支持方法或选项。

[编辑]

上面的目的是找出一个有效的模式。我可以遵循其他帖子的规则和建议,但不会让事情按照我的预期和我认为正确的方式工作(编码语言是由人类创造并为人类创造的,而不是人类为编码语言创造的)。我知道每个人都为自己在学习、开发和制作社区中众所周知的模式方面取得的成就感到自豪。

上述目标是因为Array 的所有methods 都非常受欢迎。我不在乎是否在 Ruby 版本 20 中删除了一些 Array 方法。到那时我的应用程序将过时,或者有人会用更少的代码实现相同的结果。

为什么是Array

因为顺序很重要。

为什么是内部Hash

因为我想要使用它,总的来说,构建哈希的成本补偿了它提供的优化。

为什么不只是include Enumerable

因为我们只是减少了更改对象的方法的数量,但实际上我们并没有允许将@caches_init 更改为false 的模式,所以Hash 在下次使用时重建(所以同样的问题和Array一样)

为什么不将目标 Array 方法列入白名单并包括在内

因为那不会让我到达我想去的地方。如果我希望任何人仍然使用popshift 但我不想重新定义它们,或者甚至不得不费心管理我的mixin 并不断地使用responds_to? 怎么办? (也许锻炼有助于提高你的编码和阅读他人代码的技能,但这不是应该的)

我想去哪里

我想处于一个可以重用/继承任何类的位置,我重复一遍,任何类(无论它是否是本地的)。这是 OOP 语言的基础。如果我们不是在谈论 OOP 语言(只是在其顶部添加一些糖以使其看起来像 OOP),那么让我们保持开放的态度来分析应该运作良好的模式(不管它们是否奇怪 - 对我来说更奇怪的是没有中间级别;这是许多传统模式的症状,而这反过来又是对某些功能的支持不足的症状,这些功能比被接受的更广泛需要)。

为什么宝石要提供上述功能

好吧,让我们谦虚一下。以上是一个非常简单的案例(尽管没有涵盖)。通过使用某些人想要调用的Ruby 方式,您可能会在某些时候获得灵活性。但是,当您迁移到更大的架构时,这是有代价的。如果我想创建中间类来继承怎么办?增强了简单代码的丰富本机类,同时使其与语言保持一致。说 这不是 Ruby 的方式 比试图让语言更接近从底层升级的东西更容易。

Rails 和 Ruby 几乎被许多人“模糊”地使用,我并不感到惊讶。因为在某些时候,如果没有 Rails 支持,使用 Ruby 会带来很多麻烦。因此,我对 Rails 得到如此维护并不感到惊讶。

我为什么要重新定义 poplastfirst 方法?为了什么?它们已经实施。 为什么我应该将方法列入白名单并创建 mixin?那是面向对象还是面向方法的编程?

无论如何...我不希望有人分享我对此的看法。我确实看到了其他模式,我会继续让我的大脑找到它们。如果有人足够开放,请随时分享。有人可能会批评这种方法并且是正确的,但如果你做到了,那是因为它有效。

【问题讨论】:

  • 您的设计模式并不真正遵循 Ruby 中被视为“正常”的内容。据我所知,您正在尝试实现一个观察者模式,标准库已经有一个帮助模块。 Observable
  • 我通常也不认为从 Array 继承是好的做法。同样,ruby 的核心库提供了一种(通常)更好的方法来构造具有遍历和搜索方法的类:您在类中包含 Enumerable 模块。
  • @TomLord 都很好...这不是对其他观点的批评。这是一个问题。你的答案是包含 Enumerable 模块;它仍然有修改自我的方法(即drop)。我无法将您的答案与我的问题联系起来,尽管我希望您能得到任何反馈以了解我问错了什么,因此问题不会被理解。
  • @ForeverZer0 您能否指定正常模式是什么?感谢您对 Observable 的反馈(看起来很有趣)。但是,我认为它并不比我上面绘制的方法更简单或更好(正常与否)
  • 您可能希望澄清实际需要的最终结果行为,因为这看起来可能是XY Problem。即使手动实现它,这也不会是一个强大的解决方案。一旦有人扩展了该类或其祖先之一,使用了混入、别名、鸭子类型等,所有这一切都将被打破,这超出了您的预期,因为可能性是无穷无尽的。

标签: ruby class oop inheritance


【解决方案1】:

要按原样回答您的问题,,这没有什么宝石。这不是该语言的可能性,无论是在纯 Ruby 中还是在内部使用的 C 中。

没有检测self 何时更改的机制,也没有任何方法可以检测方法是纯的(不更改自身)还是不纯的(确实更改自身)。似乎您想要一种“自动”能够知道一种方法何时是另一种方法的方法,简而言之,这是不可能的,也不是我所知道的任何语言。

在内部(使用您的示例)Array 由 C 中的 RArray 结构支持。结构是简单的存储空间:一种查看任意内存块的方法。 C 不关心你如何选择查看内存,我可以很容易地转换这个结构的指针,并说它现在是一个指向整数数组的指针,然后改变它,它会像我一样愉快地操纵内存告诉它,没有什么可以检测到我这样做了。现在加上一个事实,任何人、任何脚本或任何 gem 都可以做到这一点,而你无法控制它,它只是表明这个解决方案在根本上和客观上是有缺陷的。

这就是为什么大多数(所有?)在对象更改时需要通知的语言都使用观察者模式。您创建一个函数来“通知”某些变化,并在需要时手动调用该函数。如果有人决定子类化您的类,他们只需要继续该模式以在它更改对象状态时引发该函数。

不存在自动执行此操作的方法。 如前所述,这是一种“选择加入”或“白名单”解决方案。如果您想继承现有对象而不是从头开始使用您自己的对象,那么您需要相应地修改其行为。

也就是说,如果您对module_evalclass_eval 等使用一些巧妙的别名和元编程,添加功能并不像您想象的那么令人生畏。

# This is 100% untested and not even checked for syntax, just rough idea

def on_changed
  # Do whatever you need here when object is changed
end

# Unpure methods like []=, <<, push, map!, etc, etc
unpure_methods.each do |name|
  class_eval <<-EOS
    alias #{name}_orig #{name}
    def #{name}(*args, &block)
      #{name}_orig(*args, &block)
      on_changed
    end
  EOS
end

【讨论】:

  • 谢谢,这真是帮手。我认为这种模式可以以某种方式与您提到的 Observable 一起使用,或者至少可以更好地对其进行上下文化......将仔细研究它。
猜你喜欢
  • 2019-02-07
  • 2023-04-07
  • 2022-01-19
  • 1970-01-01
  • 2020-04-23
  • 2021-02-03
  • 2015-02-13
  • 1970-01-01
  • 2011-11-25
相关资源
最近更新 更多