在我的Ruby应用程序中,我想克隆一个类,以便我可以对克隆进行一些细微的更改,而不会影响原始类(有关详细信息,请参阅下面的注释)。不幸的是,克隆类的行为并不像我期望的那样。具体来说,克隆类的类方法似乎无法访问常量和类变量。观察:
irb(main):001:0> class Foo
irb(main):002:1> HELLO = "Hello, world!"
irb(main):003:1> def self.say_hello
irb(main):004:2> HELLO
irb(main):005:2> end
irb(main):006:1> def self.cls_var=(val)
irb(main):007:2> @@cls_var = val
irb(main):008:2> end
irb(main):009:1> def self.cls_var
irb(main):010:2> @@cls_var
irb(main):011:2> end
irb(main):012:1> end
=> nil
irb(main):013:0> Foo.say_hello
=> "Hello, world!"
irb(main):014:0> Foo.cls_var = "Test"
=> "Test"
irb(main):015:0> Foo.cls_var
=> "Test"
irb(main):016:0> Bar = Foo.clone
=> Bar
irb(main):017:0> Bar.say_hello
NameError: uninitialized constant Class::HELLO # ???
from (irb):4:in `say_hello`
from (irb):17
from C:/Ruby193/bin/irb:12:in `<main>`
irb(main):018:0> Bar.cls_var = "Another test"
(irb):7: warning: class variable access from toplevel # Say what?
=> "Another test"
irb(main):019:0> Bar.cls_var
(irb):10: warning: class variable access from toplevel
=> "Another test"
irb(main):020:0> Foo.cls_var
=> "Another test" # Why???
这里发生了什么,以及如何解决这个问题,以便Bar在克隆之后与Foo完全相同?
的跟进
更新:对不起,伙计们,我想我不清楚为什么要这样做。所以在我的例子中,Foo
是一个gem中的一个类,其功能几乎与我想要的一个类相同。事实上,Foo
和我想要的唯一区别就是那个讨厌的HELLO
常数。我希望MyClass.say_hello
返回“你好,鲍勃!”而不是“你好,世界!”。 (在你建议覆盖say_hello
之前,在我的情况下,Foo有许多使用HELLO
和say_hello
的其他方法比我的例子中复杂得多。)
现在我可以用Foo::HELLO
更改Foo::HELLO.slice!(0, 7) << "Bob!"
,但这会改变我不想要的gem的行为。那么,如何为Foo
创建具有不同值的HELLO
的精确副本?
TLDR: Foo是gem的一部分,因此我不想编辑源代码。我想要一个行为与Foo完全相同的类,除非HELLO
设置为不同的值。
答案 0 :(得分:2)
我注意到克隆后列出了常量。 Foo.constants
和Bar.constants
都显示[:HELLO]
。
将self
添加到您的类方法似乎有效。
class Foo
HELLO = "Hi"
def self.say_hello
self::HELLO
end
end
答案 1 :(得分:1)
Ruby中克隆类的语义根本不像你认为的那样工作。您可以使用类方法解决常量问题:
class Foo
def self.say_hello
"Hello, world!"
end
end
(或者使用@MichaelDodge的回答。)
您将无法使用类变量在克隆类之间共享值。如果您有合理的理由想要共享值,则必须使用其他一些机制来执行此操作。
在您的一条评论中,您提到要克隆和修改类的原因是因为它们是gem的一部分。在这种情况下,为什么不直接分割gem并根据需要修改其来源?
答案 2 :(得分:0)
为什么不简单地继承Foo
?
class Foo
HELLO = 'hi'
end
Foo::HELLO
#=> "hi"
class Bar < Foo; end
Bar::HELLO
#=> "hi"
class Bar < Foo
HELLO = 'hello there'
end
Bar::HELLO
#=> "hello there"
答案 3 :(得分:0)
所以你可以先在原来的类中设置它,然后重新设置它,这样就不会伤害代码的其他部分:
class Foo
HELLO = "Hello, world!"
def self.say_hello
HELLO
end
def self.cls_var=(val)
@@cls_var = val
end
def self.cls_var
@@cls_var
end
end
在你的剧本中:
#set new hello
class Foo
OLD_HELLO = HELLO
HELLO = "NEW HELLO WORLD"
end
class Bar < Foo
end
Bar.say_hello
#output: => "NEW HELLO WORLD"
#reset hello back
class Foo
HELLO = OLD_HELLO
end
答案 4 :(得分:0)
所以看来这里的共识是没有简单的方法让克隆按照你在这种情况下的预期工作。有很多替代解决方案,但它们都没有按照您期望clone
的方式完成。
首先,可以通过编辑原始类来引用self::HELLO
代替HELLO
来解决常量问题:
# Before:
class Foo
HELLO = "Hello, world!"
def self.say_hello
HELLO
end
end
Bar = Foo.clone
Bar.say_hello # Error
# After:
class Foo
HELLO = "Hello, World!"
def self.say_hello
self::HELLO
end
end
Bar = Foo.clone
Bar.say_hello # => "Hello, world!"
不幸的是,这个解决方案没有解决类变量的问题,它要求你编辑Foo的源代码,如果Foo是gem或其他外部库的一部分,这可能是不可取的。
另一种解决方案是将Foo子类化而不是克隆它:
class Foo
HELLO = "Hello, world!"
def self.say_hello
HELLO
end
end
class Bar < Foo
end
Bar.say_hello # => "Hello, world!"
问题在于重新定义Bar :: HELLO不会影响Bar.say_hello
的结果,正如您对克隆类所期望的那样:
Bar.const_set :HELLO, "Hello, Bar!"
Bar.say_hello # => "Hello, world!"
总而言之,最有效的解决方案可能是手动将Foo
的源代码复制到另一个类中。这不是动态的,但结果与您对clone
的期望结果完全相同。