Computer Science Canada include and extend |
Author: | Cervantes [ Fri Jun 23, 2006 9:00 pm ] | ||||||||||||||
Post subject: | include and extend | ||||||||||||||
As an introduction to this tutorial, be sure to read wtd's tutorial on mix-ins. include By now we've all done things like:
But what really is include? Just like our old friends attr_reader and private (and others like them), include is a private class method. It takes one argument for each module you wish to mix in. What does this tell us? It tells us that it is dynamic. Let's get an example: mixing in the Enumerable module. For Enumerable to work on an object, that object has to have a working 'each' method.
output wrote: 27
42 64 [27, 42, 64] [42, 64] Or perhaps we can do something like this:
Yes, global variables -- they suck. But this code is pretty useless. It's always going to mix in the Baz module, and never the Bar module. We could make a decision on which module to include based on the methods that the class defines, but that's rather boring. What if we could decide based on the object itself? Enter extend. extend extend is just like include, except it works on individual objects, not the class itself.
output wrote: Barrry
test.rb:13: undefined method `bar' for #<Foo:0xb7c88b9c> (NoMethodError) So Bar got mixed into `a', but not into `b'. Let's look at a more interesting example. Say I wanted to make a little RPG. I've got a PlayerCharacter class set up. My PlayerCharacter will have a certain level, hit points, mana points, and a class. By class, I mean he's a Fighter, a Rogue, a Wizard... that Dungeons and Dragons geek stuff. We could set up each of the possible classes (Fighter, Rogue, Wizard) as a module and then let our PlayerCharacter object mix in that module. Not all PlayerCharacter objects will be of the same class, however, so we need to mix the module into the PlayerCharacter object, rather than the PlayerCharacter class.
Seems like it would work nicely. But let's delve in a bit more. Using super With Mixins We've all used super before. When one class inherits from another, super calls the parent's version of the method you're currently within. It does the same when mixing in modules. Notice in the above code that sarevok.is_a? Klass::Fighter returned true. It's almost as if Klass::Fighter is a parent class. Let's go back to include for a moment, for it is far easier when dealing with super.
output wrote: foooy
barrry So we can use super to call the module's version of the method. Now what about extend? Using super to try to call a method from the mixed in module is much more tricky. Let's try it and see what happens.
output wrote: increase hp by 10
This is not the output I wanted. I was looking for output hoped wrote: Level up!
increase hp by 10 Why did I not get this output? The reason has to do with the order that the methods are defined in. When the Ruby interpreter reaches the class definition, it goes through the class line by line and defines the methods. Then it reaches the line where we are creating Sarevok. This calls the initialize method, which calls the extend method. Now Klass::Fighter is mixed into the sarevok object. This brings in the new definition of `level_up'. This definition overrides the old definition. If you really wanted to use this sort of structure but have it work the way we had originally hoped, you'd have to define the instance methods after mixing in Klass::Fighter. To do this, we might use instance_eval in the initialize mehod, or perhaps use the class << self idiom from within the initialize method. However, it is important to know that doing this is far less efficient than declaring instance methods as we normally do. The reason? Read Why's article on Metaclasses. In short, defining methods within the initialize method is defining those methods as singleton methods. But the methods are all the same from object to object, which is why they are normally stored in the PlayerCharacter class. This way, however, we are creating a metaclass for each PlayerCharacter object, but all those metaclasses are essentially the same. What a waste of space that would be. Conclusion Mixins are very powerful. Dynamically mixing a module into an object at run-time, using extend, opens a world of possibilities. Ruby's dynamic nature, once again, shines. |