Instance variables, class variables, and inheritance in Ruby
I’m seeing a lot of code that indicates to me that people don’t have a complete grasp of when to use class variables in Ruby.
Here’s a quick example script that should give people a better idea of what’s going on:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class ExampleClass @variable = "foo" @@variable = "bar" def initialize @variable = "baz" end def self.test puts @variable end def test self.class.test puts @@variable puts @variable end end class ExampleSubclass < ExampleClass @variable = "1" @@variable = "2" def initialize @variable = "3" end end first_example = ExampleClass.new first_example.test puts "---" second_example = ExampleSubclass.new second_example.test |
Output:
foo 2 baz --- 1 2 3
Essentially, when you declare a class variable in a base class, it’s shared with all subclasses. Changing its value in a subclass will affect the base class and all of its subclasses all the way down the inheritance tree. This behavior is often exactly what’s desired. But equally often, this behavior is not what was intended by the programmer, and it leads to bugs, especially if the programmer did not originally expect for the class to be subclassed by someone else.
I strongly encourage Ruby developers to sit down and think about how they want their classes to behave if subclassed by someone else. Be careful about using class variables, because often what you actually wanted was an instance variable on the class object.
Great demonstration, and really handy to have sort of a minimal test case for the variables mess.
A thought though: since Ruby doesn’t have constructor chaining, wouldn’t there be a point in adding a call to “super” from initialize in the subclass?
Vrensk:
You could. But I didn’t because I didn’t want to distract from the point I was making.
Hey Bob,
This article sounds a lot like you’re encouraging rather than discouraging strict OOP. Not what I heard from you last time we spoke..
Don’t bother constructing an elaborate counter-argument… I’m too ignorant to understand what’s going on on this site anyway, and probably wouldn’t understand what you’re talking about . :-)
How’s it going?
Hey Reda!
What’ve you been up to lately?
Regarding OOP, well, technically, this post isn’t advocating anything, really. Just an explanation of how Ruby treats instance and class variables. Nothing more. But I’m still of the opinion that people misuse objects and pollute their objects with methods that should have been defined more globally on a class or module instead. Is that advocating against the use of OOP? No, not really. More like pointing out that you can have too much of a good thing and end up with a confusing API.
Good example, and if possible, could you clarify a few points?
First, as I understand it, in the first example (before the ‘
-‘), the instance method ‘test’ is called; inside this method, does self.class refer to this classes singleton (anonymous class)? If so, then this simply calls the self.test method?Similarly, I’m wondering why the self.test method returns ‘foo’ yet simply accessing returning the @variable method later in the ‘test’ instance method returns ‘bar’. Further, what does @variable = ‘foo’ outside of a method definition actually do? Does it simply create the instance variable? Does an instance variable even exist, let alone have a value before it is assigned inside a function that is called?
Thanks!
Leave a Response