A short story about OOP, interfaces and soft typing
Today is the day at the headquarters of Spectacular Foo Ltd. The rising firm of the indie gaming scene is going to release its next blockbuster title: Banal Birds.
The year is 2019, Ruby 3 is three times faster and has soft typing, what a crazy coincidence that solves everything! It was a nostalgic decision but, you, as the lead developer decided to use Ruby for this project.
You ask your supervisor Jane for a final code review.
class Game
def initialize(bird: Bird)
@bird = bird
end
def play
@bird.fly
end
end
The idea is simple, but revolutionary: a player can choose any bird and then watch it fly. This is gonna be kickass!
You reused a popular Ruby gem acts_as_a_bird
, which has the perfect implementation of Bird
:
class Bird
end
Among others, we can have owls and hummingbirds:
class Owl < Bird
def fly
puts "I'm a flying owl"
end
end
class Hummingbird < Bird
def fly
puts "I'm a flying hummingbird"
end
end
Isn’t this a marvelous piece of software engineering? Well, despite the huge investment in this game there is a problem: “What about penguins?” asks Jane. Oh right, penguins are birds but they can’t fly…
class Penguin < Bird
# LOL I'm a penguin
end
Today is the launch day and you can’t afford to fix this bug. The show must go on, so you have to release the game and hope the players won’t choose a penguin.
You feel bad, and start questioning your choices: why did you use Ruby? Why soft typing?
“What you really wanted to use are interfaces, not types” says Jane. Your first reaction is “OMG, Java!”, but she takes the time to explain that the idea is borrowed from Go.
“In a dynamically typed language like Ruby, what you really care about is behavior. You want to be able to send messages to objects, which is the essence of OOP.”
She goes on: “Using types, you’re accepting objects that potentially do too much for what you need and this is a violation of the Interface Segregation Principle.”
“The difference is subtle: you’re speculating that Bird
responds to #fly
, but that isn’t always true. Instead, the language should provide a tool to check if a given object implements a method (or a set of methods)”.
At this point you’re shocked. You really wished that Ruby had introduced interfaces as special modules to define a set of behaviors.
interface Flying
def fly
puts "I'm a flying #{ name }"
end
def name
"bird"
end
end
class Falcon
include Flying
def name
"falcon"
end
end
class Game
def initialize(bird: Flying)
@bird = bird
end
def play
@bird.fly
end
end
You also wished that the VM would have stopped you from embarrassing errors like the penguin that can’t fly.