Menu
Coddy logo textTech

Duck Typing

Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 33 of 57.

Ruby does not check an object's class before calling a method; it only needs the object to have the method. "If it walks like a duck and quacks like a duck, it is a duck." This is duck typing: classes that share no superclass can still be used in the same place:

class Duck
  def speak
    "Quack"
  end
end

class Robot
  def speak
    "Beep"
  end
end

class Radio
  def speak
    "...news..."
  end
end

[Duck.new, Robot.new, Radio.new].each { |thing| puts thing.speak }

Output:

Quack
Beep
...news...

Built-in classes take part too. A method that only uses << works with an array, a string or any object of yours that defines <<:

class Tally
  attr_reader :count

  def initialize
    @count = 0
  end

  def <<(_item)
    @count += 1
    self
  end
end

def export(items, target)
  items.each { |item| target << item.to_s }
  target
end

p export([1, 2], [])
p export([1, 2], +"")
puts export([1, 2, 3], Tally.new).count

Output:

["1", "2"]
"12"
3

respond_to?(:name) asks whether an object has a public method, without calling it. Use it when an object may or may not support an optional feature:

class Printer
  def print_page(text)
    "printing #{text}"
  end
end

class Scanner
end

[Printer.new, Scanner.new].each do |device|
  if device.respond_to?(:print_page)
    puts device.print_page("report")
  else
    puts "#{device.class} cannot print"
  end
end

Output:

printing report
Scanner cannot print

When the object does not have the method, the call raises NoMethodError, whose message names the method and the object. Duck typing relies on every object that is passed in answering the methods it is sent:

def loudest(things)
  things.map(&:speak).max_by(&:size)
end

class Cat
  def speak
    "Meow"
  end
end

puts loudest([Cat.new, Cat.new])
begin
  loudest([Cat.new, 42])
rescue NoMethodError => e
  puts e.message
end

Output:

Meow
undefined method `speak' for an instance of Integer
challenge icon

Challenge

Easy

Complete the classes Printer and LineCounter and the helper export(items, target) in functions.rb. export sends every item to the target with target << item.to_s and returns the target. Printer#<< puts the text with a line number, 1: text, and returns the printer. LineCounter#<< counts lines and characters and returns the counter; its to_s returns 3 lines, 12 chars.

The supplied code reads items, exports them to an array (printed with p), to a new string (printed with p), to a Printer and to a LineCounter (printed with puts).

Your code goes in printer.rb, line_counter.rb and functions.rb. main.rb holds the supplied input/output code and cannot be edited.

Try it yourself

require_relative 'printer'
require_relative 'line_counter'
require_relative 'functions'

# Supplied input/output code: keep it as it is
input = []
while (line = gets)
  input << line.chomp
end
p export(input, [])
p export(input, +"")
export(input, Printer.new)
puts export(input, LineCounter.new)
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Ruby compiler