A singleton method is a method that belongs to a single object rather than to an entire class. Many of the methods in the Ruby class library are singleton methods. This is because, as mentioned earlier, each class is an object of the type Class. Or, to put it simply, the class of every class is Class. This is true of all classes—both those you define yourself and those provided by the Ruby class library:
class_classes.rb
class MyClass end puts( MyClass.class ) #=> Class puts( String.class ) #=> Class puts( Object.class ) #=> Class puts( Class.class ) #=> Class puts( IO.class ) #=> Class
Now, some classes also have class methods—that is, methods that belong to the Class object itself. In that sense, these are singleton methods of the Class object. Indeed, if you evaluate the following, you will be shown an array of method names that match the names of IO class methods:
p( IO.singleton_methods )
This displays the following:
[:new, :open, :sysopen, :for_fd, :popen, :foreach, :readlines, :read, :binread, :select, :pipe, :try_convert, :copy_stream]
As explained earlier, when you write your own class methods, you do so by prefacing the method name with the name of the class:
def MyClass.classMethod
It turns out that you can use a similar syntax when creating singleton classes for specific objects. This time you preface the method name with the name of the object:
def myObject.objectMethod
class_hierarchy.rb
Let’s look at a concrete example. Suppose you have a program containing Creature objects of many different species (maybe you are a veterinarian, the head keeper at a zoo, or, like the author of this book, an enthusiastic player of adventure games); each creature has a method called talk
that displays the vocal noise each creature usually makes.
Here’s my Creature class and a few creature objects:
singleton_meth1.rb
class Creature def initialize( aSpeech ) @speech = aSpeech end def talk puts( @speech ) end end cat = Creature.new( "miaow" ) dog = Creature.new( "woof" ) budgie = Creature.new( "Who's a pretty boy, then!" ) werewolf = Creature.new( "growl" )
Then you suddenly realize that one of those creatures, and one alone, has additional special behavior. On the night of a full moon, the werewolf not only talks (“growl”) but also howls (“How-oo-oo-oo-oo!”). It really needs a howl
method.
You could go back and add such a method to the Creature class, but then you’d end up with howling dogs, cats, and budgies too—which is not what you want. You could create a new Werewolf class that descends from Creature, but you will only ever have one werewolf (they are, alas, an endangered species), so why do you want a whole class for just that? Wouldn’t it make more sense to have a werewolf object that is the same as every other creature object except that it also has a howl
method? Okay, let’s do that by giving the werewolf its very own singleton method. Here goes:
def werewolf.howl puts( "How-oo-oo-oo-oo!" ) end
Heck, you can do better than that! It howls only on a full moon, so let’s make sure that, if asked to howl when the moon is new, it just growls. Here’s my finished method:
def werewolf.howl if FULLMOON then puts( "How-oo-oo-oo-oo!" ) else talk end end
Notice that, even though this method has been declared outside the Creature class, it is able to call the instance method talk
. That’s because the howl
method now lives “inside” the werewolf object so has the same scope within that object as the talk
method. It does not, however, live inside any of the werewolf’s fellow creatures; the howl
method belongs to him and him alone. Try to make the budgie.howl
, and Ruby will inform you that howl
is an undefined method.
Now, if you are debugging your code for your own use, having your program blow up thanks to an undefined method may be acceptable; however, if your program does so out in the big, bad world of the “end user,” it is definitely not acceptable.
If you think undefined methods are likely to be a problem, you can take avoidance measures by testing whether a singleton method exists before trying to use it. The Object class has a singleton_methods
method that returns an array of singleton method names. You can test a method name for inclusion using the Array class’s include?
method. In singleton_meth2.rb, for example, I’ve programmed an “open the box” game, which has a number of Box objects, only one of which, when opened, contains the star prize. I’ve named this special Box object starprize
and given it a singleton method called congratulate
:
singleton_meth2.rb
starprize = Box.new( "Star Prize" ) def starprize.congratulate puts( "You've won a fabulous holiday in Grimsby!" ) end
The congratulate
method should be called when the starprize
box is opened. This bit of code (in which item
is a Box object) ensures that this method (which does not exist in any other object) is not called when some other box is opened:
if item.singleton_methods.include?("congratulate") then item.congratulate end
An alternative way of checking the validity of a method would be to pass that method name as a symbol (an identifier preceded by a colon) to the Object class’s respond_to?
method:
if item.respond_to?( :congratulate ) then item.congratulate end
You’ll see another way of handling nonexistent methods in Chapter 20.