Objects and Classes8
Ruby is an object-oriented programming language; this chapter will show you what that really means. Like all modern languages, Ruby supports object-oriented notions like classes, inheiritance, and polymorphism. But Ruby goes further than other languages you may have used. Some languages are strict and some are permissive; Ruby is one of the most permissive languages around.
Strict languages enforce strong typing, usually at compile type: a variable defined as an array can't be used as another data type. If a method takes an array as an argument, you can't pass in an array-like object unless that object happens to be a subclass of the array class or can be converted into an array.
Ruby enforces dynamic typing, or duck typing ("if it quacks like a duck, it is a duck"). A strongly typed language enforces its typing everywhere, even when it's not needed. Ruby enforces its duck typing relative to a particular task. If a variable quacks like a duck, it is oneassuming you wanted to hear it quack. When you want "swims like a duck" instead, duck typing will enforce the swimming, and not the quacking.
Here's an example. Consider the following three classes, Duck, Goose, and DuckRecording:
class Duck def quack 'Quack!' end def swim 'Paddle paddle paddle…' end end class Goose def honk 'Honk!' end def swim 'Splash splash splash…' end end class DuckRecording def quack play end def play 'Quack!' end end
If Ruby was a strongly typed language, a method that told a Duck to quack would fail when given a DuckRecording. The following code is written in the hypothetical language Strongly-Typed Ruby; it won't work in real Ruby.
def make_it_quack(Duck duck) duck.quack end make_it_quack(Duck.new) # => "Quack!" make_it_quack(DuckRecording.new) # TypeException: object not of type Duck
If you were expecting a Duck, you wouldn't be able to tell a Goose to swim:
def make_it_swim(Duck duck) duck.swim end make_it_swim(Duck.new) # => "Paddle paddle paddle…" make_it_swim(Goose.new) # TypeException: object not of type Goose
Since real Ruby uses duck typing, you can get a recording to quack or a goose to swim:
def make_it_quack(duck) duck.quack end make_it_quack(Duck.new) # => "Quack!" make_it_quack(DuckRecording.new) # => "Quack!" def make_it_swim(duck) duck.swim end make_it_swim(Duck.new) # => "Paddle paddle paddle…" make_it_swim(Goose.new) # => "Splash splash splash…"
But you can't make a recording swim or a goose quack:
make_it_quack(Goose.new) # NoMethodError: undefined method 'quack' for # make_it_swim(DuckRecording.new) # NoMethodError: undefined method 'swim' for #
Over time, strict languages develop workarounds for their strong typing (have you ever done a cast when retrieving something from an Java collection?), and then workarounds for the workarounds (have you ever created a parameterized Java collection using generics?). Ruby just doesn't bother with any of it. If an object supports the method you're trying to use, Ruby gets out of its way and lets it work.
Ruby's permissiveness is more a matter of attitude than a technical advancement. Python lets you reopen a class after its original definition and modify it after the fact, but the language syntax doesn't make many allowances for it. It's sort of a dirty little secret of the language. In Ruby, this behavior is not only allowed, it's encouraged. Some parts of the standard library add functionality to built-in classes when imported, just to make it easier for the programmer to write code. The Facets Core library adds dozens of convenience methods to Ruby's standard classes. Ruby is proud of this capability, and urges programmers to exploit it if it makes their lives easier.
Strict languages end up needing code generation tools that hide the restrictions and complexities of the language. Ruby has code generation tools built right into the language, saving you work while leaving complete control in your hands (see Chapter 10).
Is this chaotic? It can be. Does it matter? Only when it actually interferes with you getting work done. In this chapter and the next two, we'll show you how to follow common conventions, and how to impose order on the chaos when you need it. With Ruby you can impose the right kind of order on your objects, tailored for your situation, not a one-size-fits all that makes you jump through hoops most of the time.
These recipes are probably less relevant to the problems you're trying to solve than the other ones in this book, but they're not less important. This chapter and the next two provide a general-purpose toolbox for doing the dirty work of actual programming, whatever your underlying purpose or algorithm. These are the chapters you should turn to when you find yourself stymied by the Ruby language itself, or grinding through tedious makework that Ruby's labor-saving techniques can eliminate. Every other chapter in this book uses the ideas behind these recipes.