Overloading Methods

Problem

You want to create two different versions of a method with the same name: two methods that differ in the arguments they take.

Solution

A Ruby class can have only one method with a given name. Within that single method, though, you can put logic that branches depending on how many and what kinds of objects were passed in as arguments.

Here's a Rectangle class that represents a rectangular shape on a grid. You can instantiate a Rectangle in one of two ways: by passing in the coordinates of its top-left and bottom-left corners, or by passing in its top-left corner along with its length and width. There's only one initialize method, but you can act as though there were two.

# The Rectangle constructor accepts arguments in either of the following forms: # Rectangle.new([x_top, y_left], length, width) # Rectangle.new([x_top, y_left], [x_bottom, y_right]) class Rectangle def initialize(*args) case args.size when 2 @top_left, @bottom_right = args when 3 @top_left, length, width = args @bottom_right = [@top_left[0] + length, @top_left[1] - width] else raise ArgumentError, "This method takes either 2 or 3 arguments." end # Perform additional type/error checking on @top_left and # @bottom_right… end end

Here's the Rectangle constructor in action:

' Rectangle.new([10, 23], [14, 13]) # => # Rectangle.new([10, 23], 4, 10) # => # Rectangle.new # => ArgumentError: This method takes either 2 or 3 arguments.

 

Discussion

In strongly typed languages like C++ and Java, you must often create multiple versions of the same method with different arguments. For instance, Java's StringBuffer class implements over 10 variants of its append method: one that takes a boolean, one that takes a string, and so on.

Ruby's equivalent of StringBuffer is StringIO, and its equivalent of the append method is StringIO#<<. In Ruby, that method can only be defined once, but it can take an object of any type. There's no need to write different versions of the method for taking different kinds of object. If you need to do type checking (such as making sure the object has a string representation), you put it in the method body rather than in the method definition.

Ruby's loose typing eliminates most of the need for method overloading. Its default arguments, variable-length argument lists, and (simulated) keyword arguments eliminate most of the remaining cases. What's left? Mainly methods that can take two completely different sets of arguments, like the Rectangle constructor given in the Solution.

To handle these, write a method that takes a variable number of arguments, and give it some extra code at the front that figures out which set of arguments was passed. Rectangle#initialize rejects argument lists that are of the wrong length. Additional code could enforce duck typing to make sure that the arguments passed in are of the right type. See Recipe 10.16 for simple ways to do argument validation.

See Also

Категории