CSC/ECE 517 Spring 2013/ch1b 1n jp

From Expertiza_Wiki
Jump to navigation Jump to search

Ruby's method_missing(): Advantages/Disadvantages and Alternatives

Introduction

One of the distinguishing features of the Ruby programming language is it's ability to easily extend classes in a dynamic fashion to perform a variety of different methods depending upon the needs of the current application. For example, should the developer want to extend a core language class, take String for example, they could easily re-open that class and add new methods dynamically to this existing class, rather than creating an entirely new class.

Even more powerful, Ruby allows for a construct called method_missing(). By overriding this method in your class, method_missing() will attempt to handle any method calls not currently defined by the given class (or class hierarchy). Typically, some sort of regular expression is used to determine if the method call can be redirected to an existing method based on it's naming convention (for example, a method call in a non-existent singular form could be redirected to an existing pluralized form). If the method_missing() definition cannot determine where to redirect the method call, the default is simply to call the parent class via super().

By having the ability to allow a class to dynamically extend itself even if a method definition never truly existed, Ruby allows for a far greater degree of flexibility than does a statically typed language such as Java, where much greater effort is involved in handing method calls that do not currently exist. With method_missing() defined, almost any sort of method call can be handled in the desired manner.

This article will explore the method_missing() construct in greater detail, giving both advantages and disadvantages of having this feature in the Ruby language. We will also examine how other programming languages attempt to handle the same problem.

Advantages of method_missing()

1. Allows for dynamically invoked methods.

Disadvantages of method_missing()

Alternatives to method_missing() in other programming languages

Smalltalk: doesNotUnderstand

In many ways the inspiration for Ruby, Smalltalk is often considered one of the original truly Object-Oriented languages. Smalltalk, much like Ruby with method_missing(), sends a message called #doesNotUnderstand<ref>http://c2.com/cgi/wiki?DoesNotUnderstand</ref> to handle methods that are unknown.

Essentially, when an object receives a method that it doesn't recognize, the Smalltalk runtime engine is then responsible for turning this message into an object with all necessary parameters and returning it to the sender as an exception. The sender is, at that point, responsible for handling the message and somehow dealing with the exception. Even though Smalltalk does consider this an exception, this construct is used quite frequently to form proxy objects and can handle a wide variety of different methods and arguments.

Consider the following code: <ref>http://www.iam.unibe.ch/~akuhn/blog/category/smalltalk/</ref>

   OrderedCollection subclass: #Group.
   Group >> eachRespondsTo: aSelector
       self allSatisfy: [ :each | each respondsTo: aSelector ]
   Group >> doesNotUnderstand: aMessage
       (self eachRespondsTo: aMessage selector)
         ifTrue: [ self collect: [ :each | aMessage sendTo: each ] ]
         ifFalse: [ super doesNotUnderstand: aMessage ]
   Group >> respondsTo: aSelector
     ^ (super respondsTo: aSelector) or: [ self eachRespondsTo: aSelector ]

In this code snippet, an OrderedCollection object is created. Should this collection class not understand any of the methods being sent to it, the doesUnderstand code will attempt to handle the message itself. Without overriding this method, the default is for Smalltalk to open a debugger to step through the exception (defining it overrides this behavior).

Java: DynamicProxy

In Java, the ability to handle undefined methods is a bit more cumbersome, but it can still be done successfully through the use of dynamic proxy classes. By creating this dynamic proxy, all calls will essentially be intercepted prior to invocation. An attempt will be made to invoke the object in question and, if unable to do so, an interface will need to be defined to handle the missing method scenarios. To give an example<ref>http://www.jroller.com/ie/entry/methodmissing_in_java</ref>:

 public class Dynamic implements java.lang.reflect.InvocationHandler  {
       Object delegate;
       public Dynamic(Object delegate){
               this.delegate=delegate;
       }        
       public Object invoke(Object proxy, Method method, Object[] args) 
                          throws Throwable {
               Object result = null;
               try {                  
                   result = method.invoke(delegate, args);
               } catch (InvocationTargetException e) {
                       result = 
                 ((MethodMissing)delegate).methodMissing(method,args);
               } catch (Exception e) {
                   result = 
            ((MethodMissing)delegate).methodMissing(method,args);
               } finally {
                   
               }
               if(result==delegate)
                       result = proxy;
               
               return result;
       }
       public static T newInstance(Object o,Class... interfaces) {
               return (T)java.lang.reflect.Proxy.newProxyInstance(
                   interfaces[0].getClassLoader(),
                   interfaces,
                   new Dynamic(o));
       }        
 }

Here, we define our Proxy class. Note, both an Object and Method are passed in as parameters. When attempting to invoke the method of the given Object, an exception is caught and delegated to a MethodMissing interface, which will also need to be defined.

 public interface MethodMissing {
       public T methodMissing(Method method,E... args);        
 }

Now, we define an interface whose methods will need to be implemented in order to handle the missing objects.

 public class Ruby implements MethodMissing{
       public Object methodMissing(Method method, Object... args) {
               System.out.println(method.getName());
               return this;
       }
       public static void main(String[] args){
               IDynamic dynamic = 
                  Dynamic.newInstance(new Ruby(), IDynamic.class);               
               dynamic.meow().woof();
               dynamic.woof();
               
       }               
 }
 public interface IDynamic extends MethodMissing{
       public IDynamic woof();
       public IDynamic meow();
 }

Lastly, we create a class that attempts to utilize the MethodMissing construct. As you can see, the Ruby class creates an implementation of the methodMissing() method, choosing to simply reflect the name of the method itself. When the IDynamic class is created, it attempts to run several methods which are never actually defined. Nevertheless, all of these method calls are actually intercepted by our proxy class and called as if there were no issue.

The output of running this class would be:

 meow
 woof
 woof

Python: __getattr__

Perl: AUTOLOAD

References

<references />