Duck typing is an object oriented design technique that lets us determine an object by using its properties and its methods. We are not concerned with the class type of a duck object, it's the public interface that will dictate how we may interact with the object. Our goal is to utilize a duck's public interface with other similar ducks and allow them to share their interfaces in an abstract manner. We are trying to recognize the places in our code that our application would benefit from having similar interfaces.
If we have a common behavior among our classes, they can share a public interface.
class BodyShop def repair ### preforms body work ### end end class GasMechanic def repair ### fixes gas engines #### end end class UpholsteryService def repair #### services interior ### end end
If need be, that interface can except
self for which ever attributes our duck object may need. In our example below, the
BodyShop object doesn't care about the car's engine or upholstery, it only cares that the object being passed to itself has something body related that it can respond to.
class Car attr_reader :body, :engine, :upholstery, def fix_at(repair_shop) repair_shop.repair(self) end end gto68.fix_at(BodyShop.new) gto68.fix_at(GasMechanic.new) gto68.fix_at(UpholsteryService.new)
Car class doesn't know what type of object was passed in for
#fix_at()'s argument, nor should it care. If the object has the behavior of
#repair() and does it like a
GasMechanic and does other
GasMachanic things, then it must be a
GasMechanic. This is where we apply "If an object quacks like a duck and walks like a duck, then its class is immaterial, it’s a duck".
We are looking to create an object that trusts all others to be what it expects at any given moment, and any of those objects can be any kind of thing. What helps direct us, is the fact that the
#fix_at() method serves a single purpose. Its argument arrives wishing to accomplish a single goal, it's class is less important than what the
#fix_at() needs to do. Its goal is to repair something, and the design allows the passing object to do so.
We may already have ducks hidden somewhere in our codebase. A good indication may be the usage of some of the methods below.
casestatements that switch on class
case statements that switch on class is a sign of a hidden duck.
kind_of? will also directly ask if it is of a specific class.
responds_to? is better because it helps remove the hard coded class name, but we can still do a lot better.
parts.each do |part| case part when Wheel part.polish_rim(wheels) when Window part.wipe_with_cleaner(windshield) when Body part.gently_wash(paneling) end end if part.kind_of?(Wheel) part.polish_rim(wheels) elsif part.kind_of?(Window) ## omitting the rest ## if part.responds_to?(:polish_rim) part.polish_rim(wheels) elsif part.responds_to?(:wipe_with_cleaner) ## omitting the rest ##
In the first section of our example, we depend on a concrete class, which makes it dangerous to extend. These types of methods that try to find out who the object is an instance of, in order to figure out what they do, breaks the type of trust for objects to collaborate on an abstract level. Its the interface that matters, not the class of the object that implements it. Flexible applications are built on objects that operate on trust; it is our job to make your objects trustworthy. This type of code also introduces dependencies that make code difficult to change.
All of these
part objects share something in common, on a higher abstract level, they clean something.
class Car def clean parts.each do |part| part.wash(self) end end end
This makes it easy to extend, if we were ever to add an addtional class to our
parts array, the only behavior we would need from it is for a
wash public interface.