Computer Science Canada Dynamic Classes |
Author: | Cervantes [ Mon Mar 20, 2006 8:24 pm ] | ||||||||||||||||
Post subject: | Dynamic Classes | ||||||||||||||||
Classes in Ruby are dynamic. I say this for two reasons. Re-opening Classes First, we can declare a class, do something else, then reopen the class later. The most common example of this is reopening one of the standard library classes to add a method.
Output wrote: [1, 2, 3]
[4, 5, 6] [7, 8, 9] The Creation of a Class The second reason classes are dynamic is because the interpreter goes through the class much as it would your main program.
Output wrote: Hello from the Test Class!
The Test Class is defined and the code within it executed. Notice that we did not have to create a new Test object to get the "Hello..." message. That happened upon defining the object - no more. We could write an entire program within a class, and it would run fine. Wrap Your Program in a Class We can wrap a standard program -- one that contains methods and input and output, for example -- inside a Class, and it will run fine. Our "standard program" can even involve Classes of its own. This means we would have a class within a class.
The output to this program is all detailed by the program itself. It works as expected. 42 is output as the value of bar for the inside_foo object, and 4 is output as the value of bar for the outside_foo object. Living within "Object" In the first few minutes of learning Ruby, you don't see any object orientation.
But there's OOP just below the surface.
We're actually living within the Object class. It's as if we've got:
This brings new meaning to the phrase, "Everything in Ruby is an Object". Eval We can get more dynamic, though. We can use the power of eval and its many forms to do some awesome things. eval takes a string or block of code and interprets it. We can use instance_eval, class_eval, and module_eval, to evaluate code at different levels.
output wrote: Eric
Clapton test2.rb:27: undefined method `first' for #<Person:0xb7d4f5ac @names=["Elvis"]> (NoMethodError) For Eric Clapton, we gave the initialize method a string that contained two names, "Eric" and "Clapton". When we determined that this Person object has two names, we create methods first and last to get the first and last name of the Person. This is done dynamically by passing a block of code to instance_eval. However, Elvis Presley only gave his first name, so the first and last methods were never created. The code you pass to eval can be dynamic, itself. Let's look at another example, this time using class_eval. Let's say I have a module that defines a bunch of methods that I need to use in a class. However, I can't use them quite as they are. I want to change them slightly, but all in the same way. What should we do? For starters, let's make the assumption that I'm programming an IRC client in Ruby. The client needs to accept lines of input from the server, parse them, then display and log them, all nicely formatted. The format for the displayed and logged line will be the same, so we shouldn't have to rewrite the code to parse the string. Don't Repeat Yourself; DRY. Let's create a module that will parse the input. It would define a lot of methods (one method for each IRC command), but we'll simplify this example to only show the PRIVMSG command.
This will write "Minsc: Hey wtd!" in the log file. We could then use the same trick in the Display class, where we need to modify the methods just slightly to add the name of the server to the beginning of the string, and we need to 'puts' that new line. Now we've used one module to format a line for two classes and created all the methods we need in those classes. We have avoided repeating ourselves. Without the class_eval trick, we would have had to manually create one method for each IRC command for each of our two classes. |
Author: | Cervantes [ Sun Apr 02, 2006 4:35 pm ] | ||||||||||||
Post subject: | |||||||||||||
We've seen some pretty dynamic things happening, thus far. But there's more. Renaming Methods The names of methods can be changed using alias or alias_method. Let's look at a simple example of alias.
Output wrote: Barrrrry Barrrrry Notice the use of alias: alias new_name old_name. This suggests that alias is a keyword. alias_method, on the other hand, is a method.
Output wrote: Barrrrry Barrrrry Aliasing is often used when a method needs to be slightly redefined, though it still needs to use the basis of the old method.
Output wrote: Value at ID -605548906 is "The Wind Cries Mary" Aliasing is also used when a method in a child class needs to be redefined, but the original version of the method needs to stick around. Let's look at an abstract example.
Output wrote: Barrry ---- Barrry From the Bar class Notice that neither alias nor alias_method remove the original method. To do that, we must use undef. Removing Methods We can dynamically create methods using class_eval and instance_eval, but what do we do with those methods if we don't want them later? If we dynamically created them, the condition which was true when they were created may no longer be true later. To remove a method, we can use the undef keyword. Let's look at a basic example.
Output wrote: test.rb:8: undefined method `bar' for #<Foo:0xb7d18e58> (NoMethodError) Remember that the code inside a class is executed line-by-line. The bar method is defined, then it is undefined. Calling the bar method on a Foo object gives us a NoMethodError. Let's go back to our Person class, where we dynamically created two instance methods, first and last, if the name we were given had two names. Let's create an attr_writer of sorts for @name. However, we must undefine the first and last methods for the Person object if the new name does not have two names. To do this, we need to undef singleton methods. We cannot simply do undef first, last because that would try to undefine regular instance methods -- methods that exist for all Person objects. first and last are singleton methods -- they are instance methods that only exist for some Person objects. To undefine singleton methods, we must use instance_eval to undefine the methods in the context of the current instance.
output wrote: Eric Clapton John Lennon test.rb:26: undefined method `first' for #<Person:0xb7cc3390 @name=["Simon"]> (NoMethodError) As you can see, the first and last methods no longer exist for our musician object after he has been renamed to "Sting". And so they shouldn't. The ability to rename and remove methods is quite poweful and incredibly dynamic. Ruby's classes cannot be thought of as static definitions of objects. They can change and evolve. Keep this thought in the back of your mind as you're programming (not necessarily in Ruby) and look for places where a dynamic class would help to solve a problem. |
Author: | Cervantes [ Sat Apr 29, 2006 10:37 am ] | ||||||
Post subject: | |||||||
Another way to remove methods undef is one way of "removing" a method, but perhaps not the best. The Module class has a method called "remove_method" which actually removes the method. undef prevents the class from responding to its receiver. For example, assuming the Foo class already had a bar method, class Foo undef bar end prevents Foo objects from responding to calls to method bar. remove_method, on the other hand, does not act this way. It removes the method, which may or may not result the same NoMethodError being prodcued when that method is called. But if we remove a method, then try to call it, how can that not produce a NoMethodError, you ask? Easy: Inheritance. If the parent class defines a method foo, then the child class overwrites foo, then using remove_method or unde will give different results. Using undef will produce or familiar NoMethodError. Using remove_method, however, will call the parent's version of foo. Here's an example: Given the following base code:
If the following code (using undef) is placed after the base code, we get a NoMethodError:
output wrote: test.rb:16: undefined method `yar' for #<Child:0xb7d4bb74> (NoMethodError)
However, if the following (using remove_method) is placed after the base code, the Parent's version of yar is called:
output wrote: YARR!! ~Dad
remove_method is a method, not a keyword (which raises the question, why am I bolding it?). Thus, it is called differently from undef, which can be called like this: undef foo, bar, baz. remove_method does not accept the method name directly. Rather, it accepts it as a Symbol or a String: remove_method :bar, :baz. |