在Ruby中克隆类

时间:2012-11-05 15:32:36

标签: ruby

在我的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完全相同?

  

注意:此问题是对In Ruby, is there a way to 'override' a constant in a subclass so that inherited methods use the new constant instead of the old?

的跟进

更新:对不起,伙计们,我想我不清楚为什么要这样做。所以在我的例子中,Foo是一个gem中的一个类,其功能几乎与我想要的一个类相同。事实上,Foo和我想要的唯一区别就是那个讨厌的HELLO常数。我希望MyClass.say_hello返回“你好,鲍勃!”而不是“你好,世界!”。 (在你建议覆盖say_hello之前,在我的情况下,Foo有许多使用HELLOsay_hello的其他方法比我的例子中复杂得多。)

现在我可以用Foo::HELLO更改Foo::HELLO.slice!(0, 7) << "Bob!",但这会改变我不想要的gem的行为。那么,如何为Foo创建具有不同值的HELLO的精确副本?

TLDR: Foo是gem的一部分,因此我不想编辑源代码。我想要一个行为与Foo完全相同的类,除非HELLO设置为不同的值。

5 个答案:

答案 0 :(得分:2)

我注意到克隆后列出了常量。 Foo.constantsBar.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的期望结果完全相同。