虽然我同意扩展本机类型和对象是一种不好的做法,但继承它们不应该。
在一个所谓的支持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版本可能为本机类型/类提供不同的受支持方法或选项。
[编辑]
上述目的是找出一种有效的模式。我可以遵循其他帖子的规则和建议,但不会让事情按照我的意图和我认为合适的方式工作(编码语言是由人类制作的,而不是人类为编码语言制作的)。我知道每个人都为他们在学习,开发和制作以社区众所周知的模式塑造的成就感到自豪。
上述目标是因为methods
的所有Array
都非常受欢迎。我不在乎是否在Ruby的第20版中删除了一些Array的方法。到那时我的应用程序将被淘汰,或者有人会以更少的代码获得相同的结果。
为什么Array
?
因为订单很重要。
为什么内部Hash
?
因为对于我想要的用法,总体而言,构建哈希的成本补偿了它提供的优化。
为什么不只是include Enumerable
?
因为我们只是减少了改变对象的方法的数量,但我们实际上没有允许将@caches_init
更改为false
的模式,所以Hash
在下次使用时重建(与Array
相同的问题)
为什么不直接列入白名单并包含目标Array
方法?
因为那不能让我成为我想去的地方。如果我希望任何人仍然使用pop
或shift
但我不想重新定义它们,甚至不必费心去管理我的mixins并不断使用responds_to?
怎么办? (也许这种练习很有助于提高你的编码和阅读其他人的代码的能力,但这不应该是它应该是什么)
我想去哪里?
我想处于一个可以重复使用/继承任何类的位置,我重复一遍,无论是否是本机的(无论是否是本机的)。这是OOP语言的基础。如果我们不是在讨论OOP语言(但只是在它的顶部只是一些糖来使它看起来像OOP),那么让我们保持开放,分析应该运行良好的模式(无论它们是否奇怪 - 对我来说更奇怪的是,没有中间水平;这是许多传统模式的症状,这反过来又是对某些特征的支持不足的症状,这些特征比接受的要求更广泛。
为什么宝石会提供上述内容?
好吧,让我们谦虚吧。以上是一个非常简单的案例(即使没有涵盖)。通过使用某些人想要称之为Ruby方式的东西,您可以在某些时候获得灵活性。但是当你转向更大的架构时需要付出代价。如果我想创建继承的中间类怎么办?丰富的本机类,可以增强简单的代码,同时保持与语言的一致性。更容易说这不是Ruby方式,而是试图使语言更接近从底部升级的东西。
我很惊讶Rails和Ruby几乎“模糊地”被许多人使用。因为在某些时候,如果没有一些Rails支持,你对Ruby有什么麻烦。因此,我对Rails的维护并不感到惊讶。
我为什么要重新定义pop
,last
或first
方法?为了什么?它们已经实施。我为什么要将方法列入白名单并创建mixins?是一个面向对象或方法的编程?
无论如何......我不希望任何人分享我对此的看法。我确实看到了其他模式,我将继续让我的思想找到它们。如果有人足够开放,请随时分享。有人可能批评这种方法并且是正确的,但是如果你到达那里是因为它有效。
要回答你写的问题,不,没有宝石。这不是语言的可能性,无论是纯Ruby还是内部使用的C语言。
没有任何机制可以检测self
何时发生变化,也没有任何方法可以检测方法是纯粹的(不改变自我)还是不纯净(改变自我)。看起来你想要一种“自动”能够知道方法何时是一种方法的方法,并且简单地说,这是不可能的,也不是我所知道的任何语言。
在内部(使用您的示例),Array
由C中的RArray
结构支持。结构是简单的存储空间:一种查看任意内存块的方法。 C不关心你如何选择查看内存,我可以轻松地转换这个结构的指针,并说它现在是一个指向整数数组的指针并改变它的方式,它会愉快地操纵内存,因为我告诉它,没有任何东西可以检测到我这样做了。现在添加一个事实,任何人,任何脚本或任何宝石都可以做到这一点,你无法控制它,它只是表明这个解决方案从根本上和客观上有缺陷。
这就是为什么在更改对象时需要通知的大多数(全部?)语言都使用观察者模式。您可以创建一个在发生更改时“通知”的功能,并在需要时手动调用该功能。如果某人决定继承您的类,他们只需要继续该模式来提升该函数,如果它改变了对象状态。
没有这样做的自动方式。如前所述,这是一种“选择加入”或“白名单”解决方案。如果要子类化现有对象而不是从头开始使用自己的对象,则需要相应地修改其行为。
也就是说,如果你使用module_eval
,class_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