Duck typing

Let’s start by breaking down what duck typing is in the first place. The idea of types in programming languages refers to the way we categorize programming languages according to dimension. Some languages are statically typed, meaning the types are backed into the implementation of the language and enforced at compile time. Languages like Java and C++ are statically typed languages. On the contrary you have dynamically typed languages like Ruby and Python. Dynamic typing is more flexible and any type restrictions would have to be made at runtime.

Objects in Ruby, for example, are defined by the methods on them and not what type of thing they are. This idea of an object being defined by the messages it responds to is what duck typing is all about. The name duck typing comes from the saying, “If it walks like a duck, and quacks like a duck, then it must be a duck.”

In other words, if the thing behaves like a duck then we treat it like a duck, even if it isn’t an actual duck. The implementation of the Ruby language is built using duck typing and it would seem wise to build our Ruby applications in that way.

Noticing the Anti-Pattern

Let me start by showing you an example of an anti-pattern that can hide a duck type. Noticing this pattern and being able to apply the design ideas of duck typing to fix it will greatly increase your code’s flexibility and extendability. Here is what we will be designing:

Let’s think about everything that goes into making a rubber duck, and build a basic example from this. The rubber duck begins its life as hot liquid plastic which is poured into a mold and placed in an oven for 15 minutes at 150 degrees Celsius. After the duck is cooled, it is handed off to be painted and then goes through some quality assurance checks. Last but not least, our rubber duck is packaged and ready to go.

Let’s design a duck class that will respond to the prepare message. Prepare will take in N amount of workers and call each worker to do its part. Here is the code:

class Duck
  def prepare(workers)
    workers.each do |worker|
      case worker
      when Moulder
        worker.pour_mold(self)
      when Cooker
        worker.cook_duck(self)
      when Painter
        worker.paint_duck(self)
      when QualityAssurance
        worker.test_duck(self)
      when Packager
        worker.package_duck(self)
      end
    end
  end
end

Notice the subtle dependencies introduced here. Our Duck class knows the name of the class it wants and it
also knows how to get it from that class. All our duck wants is to be prepared, so we should put trust in other
classes and just send the message we actually want to send: prepare_duck.

A Proper Duck

Let’s refactor the duck factory example using duck typing:

# Our prepare method can now accept workers of any type as long as
# it can respond to prepare_duck. Without strict type restrictions
# we add a huge amount of flexibility to our application. Adding a more
# workers is trivial.

class Duck
  def prepare(workers)
    workers.each{ |worker|
      worker.prepare_duck(self) }
  end
end

class Moulder
  def prepare_duck(duck)
    puts "heat_plastic"
    puts "pour_mold"
  end
end

class Cooker
  def prepare_duck(duck)
    puts "set_oven_temp('150')"
    puts "cook_for('15min')"
  end
end

class Painter
  def prepare_duck(duck)
    puts "paint_body('yellow')"
    puts "paint_beak('orange')"
    puts "paint_eyes"
  end
end

class QualityAssurance
  def prepare_duck(duck)
    puts "float_duck"
    puts "inspect_duck"
  end
end

class Packager
  def prepare_duck(duck)
    puts "wrap_duck"
    puts "box_duck"
  end
end

# All of our classes share the same prepare_duck method signature, but they
# all have different implementations. However, in cases where you need to
# share some code across this interface you can break your duck type into a module.

d = Duck.new
d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new])

Notice now that our prepare method of our Duck class isn’t concerned with the type of object the worker is. All it needs is a worker who can prepare a duck, or in other words a duck that can quack.

Duck typing

Duck typing: Wrapping it all up

These examples should have shown you the flexibility that duck typing can bring to your application. You don’t have to look far to find good examples of this in the wild. Many of your favorite libraries and even the Ruby language itself makes heavy use of them. This is because duck typing will reduce many of the costly dependencies on classes and instead give you a more forgiving dependency on messages. This dependency on messages enforces the philosophy that our objects should trust each other. This will allow our objects to ask for what they want without knowing how to get it, and thus give us a more maintainable application.

We're building an AI-powered Product Operations Cloud, leveraging AI in almost every aspect of the software delivery lifecycle. Want to test drive it with us? Join the ProdOps party at ProdOps.ai.