1. Computing

Code Reuse: Inheritance

By

Code Reuse: Inheritance

If you strip object oriented programming down to its core, it's all about identifying the "things" in your program and the code that they share and putting them together in the form of classes. Further, there are different classes of things that are very similar, for example an administrator user and a user have the same methods, but the administrator user has some methods the user method does not. The most popular method of representing representations such as these is inheritance, or organizing your classes into a hierarchy.

If you're familiar with other object oriented programming languages, you'll be familiar with Ruby's object model. It's single inheritance, meaning a class may only inherit directly from another class (C++ and other languages support multiple inheritance, Ruby does not). There are no abstract classes or interfaces, they aren't necessary with Ruby's very late binding and duck typing. There is a related feature called a "mixin," but it's primarily used for composition and is not discussed here.

The syntax for this is simple. In a class definition statement, define the class that the new class inherits from with a less than symbol and the name of the class. For instance, an administrator user inherits its methods from the user class, so you would say class Admin < User. Any object of the class Admin will now have all the methods of the class User. Let's expand this, provide a few bogus methods, and then we'll discuss how this all fits together.


#!/usr/bin/env ruby

class User
  def login
    # Ask for username and password
  end

  def post_message
    # Post a message to the message board
  end
end

class Admin < User
  def login
    super

    # An extra step, ask for the code from an RSA key
  end

  def ban_user(u)
    # Ban a user for being naughty
  end
end

admin = Admin.new
user1 = User.new
user2 = User.new

Method Lookup

To understand what really happens here, you have to understand a little bit about how Ruby's method lookup works. If we call admin.login, which login method gets called: Admin#login or User#login? The answer should be obvious, Admin#login is called, but why? Think of the object's class and ancestors as a chain, each link holding a list of methods. You can see this chain by inspecting the results of some_obj.class.ancestors. There you'll see any classes in your own class hierarchy, as well as Object, Kernel and BasicObject.


ruby-1.9.3-p0 :002 > admin = Admin.new
 => # 
ruby-1.9.3-p0 :003 > admin.class.ancestors
 => [Admin, User, Object, Kernel, BasicObject] 
ruby-1.9.3-p0 :004 > 

When any method is called on the admin object, Ruby uses this ancestor list to find where the method really is. It will first search the Admin class, then the User class, the Object class, the Kernel module (it's not actually a class, but think of it as a class for now) and finally the BasicObject class. The first method found with a matching name is called. So, if you were to call admin.login, the match is in the first class examined, so Admin#login will be called. However, if admin.post_message is called, the lookup goes a little bit deeper. The Admin class doesn't have this method, so we go up in the ancestor chain and check the User class. A match is found, so User#post_message is called on the admin object.

Note that if you define a class that doesn't inherit from anything, it implicitly inherits from Object. In our example, the User class doesn't seem to inherit from anything, but it inherits a default set of methods from the Object class.

The 'super' Keyword

Look closely at our Admin#login method. The first line is the super keyword, what does that mean? The intent is to call User#login to do normal user authentication (asking for a username and password), then to do the extra authentication step of asking for the code from an RSA key. The super keyword will simply continue the method lookup process from the current point in the ancestor chain. It will pass any method arguments to Admin#login to User#login.

If you don't want to pass any arguments to the super method, or want to pass a different set of arguments, the super keyword acts much like any other method call with the except of the "naked" super call, which passes all arguments from the current method. Passing no arguments is achieved with super(), and any other arguments with super(arg1, arg2).

Note that super does not work with method_missing (discussed below). If none of the classes above the current class in the ancestor chain, a NoMethodError exception will be raised.

The Method is Missing

What happens if we call admin.eat_monkeys? The programmer never intended for admin users to want to eat monkeys, and it's unlikely such a method will exist in any of the classes in the ancestor chain. So what happens if we reach the end of the chain and don't find a match? In most programming languages, this would be an error but Ruby has an extra step. It will then search the ancestor chain, from the beginning, for a method called method_missing. If it doesn't find any such method, then a NoMethodError will be raised.

The method_missing method takes at least one argument, the name of the method called as a symbol. Any remaining arguments will be the arguments to the original method, which may be an unknown number so it's best to use the soak operator here. Let's implement a User#method_missing method, as users are prone to doing unexpected and illogical things.


class User
  def method_missing(method_name, *args)
    puts "The user has done something unexpected, #{method_name}"
  end
end

This is not so hard to understand, but why would you want to do this? There are two primary uses for this feature. The first is a meta-programming technique. By "catching" method calls like this, you can then parse the method name and do something more useful. For example, if the method buy_textbook('math 101') is called on User, the method_missing method may look at that and in turn perform a method call like buy(:type => :textbook, :name => 'math 101'), which is harder to type and remember.

The other use is detailed error messages. You can print whatever information you want about the erroneous method call then call the super method from within method_missing. Remember that super continues the method lookup in the ancestor chain. If it reaches the end of the chain, then a NoMethodError is raised. So, you have a chance to print some more useful error messages. Also, a super method call is usually included as the default behavior in a metaprogramming method_missing to pass on any truly unknown method.

Using inheritance, the super keyword and method_missing, you can now implement most class hierarchies you'd want. Some object oriented programmers like things a bit more complex though, and for that you'll have to use another technique called composition. Ruby provides support for this with mixin modules.

  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Object Oriented Programming
  6. Code Reuse: Inheritance

©2014 About.com. All rights reserved.