Finding a duck type (OOP)

I see this problem all too often in stack overflow: a situation where folks check for the type of a class in an if statement, and then respond accordingly. Rather than repeating myself a million times, I thought it much easier just to write a post on the subject (DRY FTW!). Anyways, this type of code (pun intended) smacks of a duck type not being identified and utilised.

(If you don’t know what a duck type is, please Google it, or read a previous post I’ve written on the topic. In short, the important thing with duck types are that what’s important is not what a class is, but rather, what the class actually does.)

Consider this code for example:

class Vehicle
end

class Car < Vehicle
end

# Assume Truck, OilTanker and JetPlane
# also inherit from Vehicle

class FuelAnswers
    def fuel_type?(vehicle)
        if vehicle.class == Car
            puts "I like to drink unleaded petrol"
        elsif vehicle.class == Truck
            puts "I like to drink diesel"
        elsif vehicle.class == OilTanker
            puts "I like to drink low grade bunker fuel"
        elsif vehicle.class == JetPlane
            puts "I like to drink unleaded kerosene"
        else
            puts "just try regular gasoline and hope for the best!"
        end
    end
end

Do you see anything that is wrong with it?

Suppose we want to create another type of vehicle? Then we would have to come back to the fuel_type? method and edit it. We don’t want to edit other things if possible. We want to be able to add things, without making changes to anything else. This is encapsulated in the principle: “leave things open for modification but closed for extension”. Accordingly such a method, as written above, will require modification when we add a new type of vehicle into the mix.

This can be avoided by using a common “duck type”.

Objects should be responsible for themselves!

If you want to know what type of food your friend likes, shouldn’t you go to him/her and ask them directly? Does it make any sense to go to Dr Phil when you find out something about your friend? You can cut out the middle man and ask your friend directly: “what do you like eating?”

    car.fuel_type?
    # => I like to drink unleaded petrol"

That would allow you to add further vehicle classes without worrying about changing the fuel_type? in the FuelAnswers class. You could then simplify the code there to this:

class FuelAnswers
    def fuel_type?(vehicle)
        vehicle.fuel_type?
        # returns the right answer no matter what
    end
end

So then your classes would look something like this:

class Car < Vehicle
    def fuel_type?
        puts "I like to drink unleaded petrol"
    end
end

class Truck < Vehicle
    def fuel_type?
        puts "I like to drink diesel"
    end
end

# etc

We’ve created a situation where you don’t need to constantly edit fuel_type? every time a class is added or subtracted to the Vehicle family. You don’t have a large if statement, constantly checking what type of class you are dealing with, and responding accordingly. Let polymorphism take care of all of that. You just send the fuel_type? message to vehicle, and you magically get the answer you are looking for, depending on whether you are dealing with a car, trunk or oil tanker etc.


# The old version of the code without using duck types:

class FuelAnswers
    def fuel_type?(vehicle)
        if vehicle.class == Car
            puts "I like to drink unleaded petrol"
        elsif vehicle.class == Truck
            puts "I like to drink diesel"
        elsif vehicle.class == OilTanker
            puts "I like to drink low grade bunker fuel"
        elsif vehicle.class == JetPlane
            puts "I like to drink unleaded kerosene"
        else
            puts "just try regular gasoline and hope for the best!"
        end
    end
end

# The new version of the code:
class FuelAnswers
    def fuel_type?(vehicle)
        vehicle.fuel_type?
        # returns the right answer no matter what
    end
end

# Remember, all the vehicle classes must implement the duck type:
# they must all contain a fuel_type? method!!

I hope this helps you.

Written on June 1, 2017