开发者

Is there a better way of doing class_eval() to extract class variables, in Ruby?

I personally don't have anything against this, apart from the fact that's is long, but what really bothers me is the word eval.

I do a lot of stuff in JavaScript and I run from anything resembling eval like it's the devil, I also don't fancy the fact that the parameter is a st开发者_StackOverflow社区ring (again, probably because it's eval).

I know I could write my own method to fix the method-name-length problem, my 'method name issue' and the parameter-being-a-string thingy, but what I really want to know is: Is there a better, shorter, fancier, yet native, way of doing class_eval to extract class variables?

Side note: I know about the existence of class_variable_get() and class_variables(), but they don't really look appealing to me; horribly long, aren't they?

EDIT: Updated the question to be more specific.

Thanks!


Use class_variable_get, but only if you must

class_variable_get is the better way, other than the fact that it is not "appealing" to you. If you are reaching inside a class and breaking encapsulation, perhaps it is appropriate to have this extra barrier to indicate that you're doing something wrong.

Create accessor methods for the variables you want to access

If these are your classes, and accessing the variables doesn't break encapsulation, then you should create class accessor methods for them to make it easier and prettier:

class Foo
  def self.bar
    @@bar
  end
end
p Foo.bar

If this is your class, however, are you sure that you need class variables? If you don't understand the implications (see below), you may actually be wanting instance variables of the class itself:

class Foo
  class << self
    attr_accessor :bar
  end
end

Foo.bar = 42
p Foo.bar

The behavior of class variables

Class variables appear to newcomers like the right way to store information at a class level, mostly because of the name. They are also convenient because you can use the same syntax to read and write them whether you are in a method of the class or an instance method. However, class variables are shared between a class and all its subclasses.

For example, consider the following code:

class Rectangle
  def self.instances
    @@instances ||= []
  end
  def initialize
    (@@instances ||= []) << self
  end
end

class Square < Rectangle
  def initialize
    super
  end
end

2.times{ Rectangle.new }
p Rectangle.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>]

Square.new
p Square.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>, #<Square:0x25c76d0>]

Ack! Rectangles are not squares! Here's a better way to do the same thing:

class Rectangle
  def self.instances
    @instances ||= []
  end
  def initialize
    self.class.instances << self
  end
end

class Square < Rectangle
  def initialize
    super
  end
end

2.times{ Rectangle.new }
p Rectangle.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>]

2.times{ Square.new }
p Square.instances
#=> [#<Square:0x25c76d0>, #<Square:0x25c76b8>]

By creating an instance variable and accesor methods on the class itself—which happens to be an instance of the Class class, similar to MyClass = Class.new—all instances of the class (and outsiders) have a common, clean location to read/write information that is not shared between other classes.

Note that explicitly tracking every instance created will prevent garbage collection on 'unused' instances. Use code like the above carefully.

Using class_eval in a cleaner manner

Finally, if you're going to use class_eval, note that it also has a block form that doesn't have to parse and lex the string to evaluate it:

Foo.class_eval('@@bar') # ugh
Foo.class_eval{ @@bar } # yum
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜