Checking Class or Module Membership

Problem

You want to see if an object is of the right type for your purposes.

Solution

If you plan to call a specific method on the object, just check to see whether the object reponds to that method:

def send_as_package(obj) if obj.respond_to? :package packaged = obj.package else $stderr.puts "Not sure how to package a #{obj.class}." $stderr.puts 'Trying generic packager.' package = Package.new(obj) end send(package) end

If you really can only accept objects of one specific class, or objects that include one specific module, use the is_a? predicate:

def multiply_precisely(a, b) if a.is_a? Float or b.is_a? Float raise ArgumentError, "I can't do precise multiplication with floats." end a * b end multiply_precisely(4, 5) # => 20 multiply_precisely(4.0, 5) # ArgumentError: I can't do precise multiplication with floats.

 

Discussion

Whenever possible, you should use duck typing (Object#respond_to?) in preference to class typing (Object#is_a?). Duck typing is one of the great strengths of Ruby, but it only works if everyone uses it. If you write a method that only accepts strings, instead of accepting anything that supports to_str, then you've broken the duck typing illusion for everyone who uses your code.

Sometimes you can't use duck typing, though, or sometimes you need to combine it with class typing. Sometimes two different classes define the same method (especially one of the operators) in completely different ways. Duck typing makes it possible to silently do the right thing, but if you know that duck typing would silently do the wrong thing, a little class typing won't hurt.

Here's a method that uses duck typing to see whether an operation is supported, and class typing to cut short a possible problem before it occurs:

def append_to_self(x) unless x.respond_to? :<< raise ArgumentError, "This object doesn't support the left-shift operator." end if x.is_a? Numeric raise ArgumentError, "The left-shift operator for this object doesn't do an append." end x << x end append_to_self('abc') # => "abcabc" append_to_self([1, 2, 3]) # => [1, 2, 3, […]] append_to_self({1 => 2}) # ArgumentError: This object doesn't support the left-shift operator. append_to_self(5) # ArgumentError: The left-shift operator for this object doesn't do an append. 5 << 5 # => 160 # That is, 5 * (2 ** 5)

An alternative solution approximates the functionality of Java's interfaces. You can create a dummy module for a given capability, have all appropriate classes include it, and use is_a? to check for inclusion of the module. This requires that each participating class signal its ability to perform a certain task, but it doesn't tie you to any particular class hierarchy, and it saves you from calling the wrong method just because it has the right name.

module ShiftMeansAppend def <<(x) end end class String include ShiftMeansAppend end class Array include ShiftMeansAppend end def append_to_self(x) unless x.is_a? ShiftMeansAppend raise ArgumentError, "I can't trust this object's left-shift operator." end x << x end append_to_self 4 # ArgumentError: I can't trust this object's left-shift operator. append_to_self '4' # => "44"

 

See Also

Категории