1. Computing

Object Specific Behavior

By

Object Specific Behavior

We've talked before about the Ruby method call lookup procedure and the ancestor chain. However, there are two more thing that must be considered: singleton classes and mixin modules on single objects.

Extending Objects

We've talked before about including modules in classes to expand a class's functionality, but mixin modules can also be applied to single objects. This limits the scope of the behavior to single objects.

In this example, Alice and Bob and both Person objects. However, Bob has a special talent: he can juggle. We'll define a mixin module called Juggler and extend Bob's object with it.


#!/usr/bin/env ruby

class Person
  def initialize(name)
    @name = name
  end

  def say_hello
    puts "Hello, I am #{@name}"
  end
end

module Juggler
  def juggle
    puts "Look at me!  I'm juggling!"
  end
end

alice = Person.new('Alice')
bob = Person.new('Bob')

bob.extend(Juggler)

bob.juggle   # Bob can juggle
alice.juggle # Alice cannot, will raise exception

In this example, only Bob's ancestor chain will include the Juggler module, Alice's ancestor chain will not. This is different than if we had used an include Juggler statement in the Person class definition. If that were the case, all Person objects would be able to juggle. Since we extended a single object with the Juggler module, only certain objects are Jugglers.

Singleton Classes

Recall the ancestor chain for a typical class. B is a child class of A, which is a child class of Object. When a method is called, B's methods will be searched, then A's, Object's and so forth up the chain. But the object itself has what's called a singleton class. The singleton class sits at the head of the ancestor chain before any of the other classes (including the object's actual class). All singleton classes are unique to the object they belongs to.

Singleton classes are used to define object-specific behavior, as opposed to class-specific behavior. Since each object has a unique singleton class, the methods in that singleton class only affect that single object (which is why they're called singleton classes). Singleton classes are often called eigenclasses or anonymous classes.

To access a class's singleton class, the class <<object syntax is used. Instead of referring to a class by a constant name such as Player, you are accessing the object's singleton (or "anonymous," since it has no name) class.

In this example, there are two Person objects: bob and alice. Bob has a special skill that makes him different than Alice and all other Person objects: Bob can juggle. To implement this, we'll access Bob's singleton class using the special class syntax and add the juggle method.


#!/usr/bin/env ruby

class Person
  def initialize(name)
    @name = name
  end

  def say_hello
    puts "Hello, I am #{@name}"
  end
end

alice = Person.new('Alice')
bob = Person.new('Bob')

class <<bob
  def juggle
    puts "Bob juggles three colored balls"
  end
end

bob.juggle  # Bob can juggle
alice.juggle  # Alice cannot, this will raise an exception

In this example, Alice's ancestor chain would be just as you'd expect: Person, Object, Kernel. However, Bob's ancestor chain will be slightly different, there is an invisible class before the Person class that holds our juggle method. However, it won't show up on a call such as bob.class.ancestors. It isn't technically an ancestor, so how do we get some more information about a singleton class?

Getting Singleton Class Objects

In Ruby before 1.9.2, getting the singleton class object (the object of the type Class that represents the singleton class of the object) was a bit convoluted. It leveraged what we talked about before with statements inside class statements. It also leverages the fact that class statements themselves evaluate to the value of their last expression. So, you just open up the singleton class with the same syntax as you'd use to define methods and return self.


bob = Person.new
bobs_singleton = class <<bob; self; end

In Ruby 1.9.2 and later there's a method for doing this a little more cleanly. Simply call the singleton_class method on any object.


bob = Person.new
bobs_singleton = bob.singleton_class

Once you have the singleton class, you can treat it like any other class object. You can query its methods, define new methods, etc. However, if all you want is a list of the singleton methods, you can use the singleton_methods method on any object.


bobs_singleton_methods = bob.singleton_methods

Monkeypatching Using Class Specific Behavior

Now that you've learned two way to implement class specific behavior, you might be wondering what they're actually used for. One big use for these is the monkeypatch. A monkeypatch changes the fundamental behavior of an object to add functionality or often to fix a bug. Doing this is dangerous as the effects of doing such a thing are not fully understood. When monkeypatching, you generally want to limit the scope of such a patch as much as possible.

Imagine the following scenario: you have an object that collects data, crunches some numbers and prints the results using puts. However, you don't want to print this data to the screen, you want to print it to a file. Changing the source code is not an option, there are too many puts statements to consider. Changing the destination of all puts statements to be the file is not practical, you're printing other things to the terminal while this object works. What you need is a very localized monkeypatch.

When you write a puts statement, what you really mean is to call the Kernel#puts method. The Kernel module sits up quite high in the ancestor chain. By simply using either class specific behavior (extending using modules, or a singleton class), we can inject our own puts method into a single object, employing a very localized monkeypatch to fix our little problem.


n1 = NumberCruncher.new
n1.run  # This will print to the console

n2 = NumberCruncher.new
class <<n2
  # Redirect the puts to a file's puts method
  def puts(*a)
    @file.puts(*a)
  end
end

n2.run  # This will print to the file
  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Object Oriented Programming
  6. Object Specific Behavior

©2014 About.com. All rights reserved.