0

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

注意:这个问题是对在 Ruby 中的后续问题,有没有办法“覆盖”子类中的常量,以便继承的方法使用新常量而不是旧常量?


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

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

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

4

5 回答 5

2

我注意到在克隆之后列出了常量。Foo.constants并且Bar.constants都显示[:HELLO]

添加self到您的类方法似乎有效。

 class Foo
   HELLO = "Hi"
   def self.say_hello
     self::HELLO
   end
 end
于 2012-11-05T16:01:29.373 回答
1

Ruby 中克隆类的语义根本不像您认为的那样工作。您可以使用类方法解决常量问题:

class Foo
  def self.say_hello
    "Hello, world!"
  end
end

(或者使用@MichaelDodge 的答案。)

您将无法使用类变量在克隆的类之间共享值。如果你有正当的理由想要共享一个值,你将不得不使用其他一些机制来做到这一点。

在您的一条评论中,您提到要克隆和修改类的原因是因为它们是 gem 的一部分。在那种情况下,你为什么不直接 fork gem 并根据需要修改它的源代码?

于 2012-11-05T15:51:19.543 回答
0

因此,似乎这里的共识是,没有简单的方法可以让克隆按照您在这种情况下所期望的方式工作。有很多替代解决方案,但没有一个能完全按照您期望的方式工作clone


首先,可以通过编辑原始类来self::HELLO代替 just来解决常量问题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.

于 2012-11-09T19:05:29.083 回答
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"
于 2012-11-05T15:42:53.553 回答
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
于 2012-11-05T17:42:52.457 回答