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
精彩评论