En Java ou en C#, pour appeler une méthode sur un objet, le compilateur veut d’abord savoir de quel type il est. En Ruby, cette question n’existe pas. La seule chose qui compte : l’objet répond-il au message que tu lui envoies ? C’est le duck typing, résumé par l’adage : « si ça marche comme un canard et que ça cancane comme un canard, alors c’est un canard ».
On parle à un comportement, pas à une classe
Regarde cette méthode : elle ne mentionne aucun type.
def faire_crier(animal)
animal.crier # je n'envoie qu'un message
end
class Canard; def crier = "Coin !" end
class Chat; def crier = "Miaou !" end
faire_crier(Canard.new) # => "Coin !"
faire_crier(Chat.new) # => "Miaou !"Canard et Chat n’ont aucun ancêtre commun, aucune interface partagée. Ils
fonctionnent tous les deux parce qu’ils répondent à crier. Le polymorphisme
en Ruby ne passe pas par l’héritage : il passe par le message.
Le contrat est implicite
Ce qui définit ce qu’on attend d’un objet, ce n’est pas une interface déclarée,
c’est l’ensemble des messages qu’on lui envoie. Tant qu’un objet y répond, il
convient — même un objet de test écrit en trois lignes :
faux = Object.new
def faux.crier = "(silence)"
faire_crier(faux) # => "(silence)"C’est ce qui rend les tests Ruby si légers : pas besoin de mock élaboré, il suffit d’un objet qui répond aux bons messages.
Le revers : quand le canard trahit
La souplesse a un prix. Si un objet ne répond pas au message, l’erreur n’arrive qu’à l’exécution :
faire_crier("texte") # NoMethodError: undefined method `crier' for "texte"Le contrat étant implicite, c’est à toi de le rendre lisible : un nom de méthode
clair, une documentation, parfois un respond_to? aux frontières. Le duck typing
ne supprime pas le contrat — il le déplace du compilateur vers ta discipline.
Ce que ton objet est ne regarde personne. Ce qu’il sait faire, si.
Maîtrise cette idée et tout le modèle objet de Ruby s’éclaire : le polymorphisme, les protocoles implicites, et la frontière où il faut redevenir prudent.
In Java or C#, before calling a method on an object, the compiler first wants to know what type it is. In Ruby, that question doesn’t exist. The only thing that matters: does the object answer the message you send it? That’s duck typing, captured by the saying: “if it walks like a duck and quacks like a duck, then it’s a duck.”
You talk to a behavior, not a class
Look at this method: it mentions no type at all.
def make_it_shout(animal)
animal.shout # I only send a message
end
class Duck; def shout = "Quack!" end
class Cat; def shout = "Meow!" end
make_it_shout(Duck.new) # => "Quack!"
make_it_shout(Cat.new) # => "Meow!"Duck and Cat share no common ancestor, no shared interface. They both work
because they respond to shout. Polymorphism in Ruby doesn’t go through
inheritance: it goes through the message.
The contract is implicit
What defines what we expect from an object isn’t a declared interface, it’s the
set of messages we send it. As long as an object responds to them, it fits —
even a test object written in three lines:
fake = Object.new
def fake.shout = "(silence)"
make_it_shout(fake) # => "(silence)"This is what makes Ruby tests so light: no elaborate mock needed, just an object that answers the right messages.
The flip side: when the duck betrays you
Flexibility has a price. If an object does not respond to the message, the error only shows up at runtime:
make_it_shout("text") # NoMethodError: undefined method `shout' for "text"Since the contract is implicit, it’s on you to make it legible: a clear method
name, documentation, sometimes a respond_to? at the boundaries. Duck typing
doesn’t remove the contract — it moves it from the compiler to your discipline.
What your object is is nobody’s business. What it can do, is.
Master this idea and Ruby’s whole object model lights up: polymorphism, implicit protocols, and the boundary where you need to become careful again.