Aucune classe n'est fermée — on peut rouvrir même String.No class is closed — you can reopen even String.
Dernier barreau de l'échelle du volume, et le plus puissant. Le № 1 interceptait, le № 2 générait dans nos classes ; ici on rouvre n'importe quelle classe — la nôtre, celle d'une gem, ou String, Integer, Array du cœur de Ruby — pour y ajouter ou y changer du comportement. Ce n'est pas une faille : une classe Ruby n'est jamais close, écrire class String une seconde fois la rouvre. La conséquence directe du Vol 1 (les classes sont des objets qu'on manipule) et du Vol 5 (la méthode s'insère dans la chaîne d'ancêtres). La puissance est totale ; sa portée aussi — et c'est précisément le danger.The volume's last rung, and the most powerful. № 1 intercepted, № 2 generated in our classes; here you reopen any class — yours, a gem's, or String, Integer, Array from Ruby's core — to add or change behavior. It's not a loophole: a Ruby class is never closed, writing class String a second time reopens it. The direct consequence of Vol 1 (classes are objects you manipulate) and Vol 5 (the method inserts into the ancestor chain). The power is total; so is its reach — and that's precisely the danger.
# une classe n'est jamais « fermée » : on la ROUVRE, même String class String def shout = upcase + "!" end "hello".shout # => "HELLO!" "".respond_to?(:shout) # => true TOUS les String l'ont désormais "x".method(:shout).owner # => String la méthode vit DANS String même # rien de spécial : on a juste ajouté un maillon à la classe String (Vol 5) String.instance_methods(false).include?(:shout) # => true
Rouvrir une classe agit sur tout le programme à la fois : chaque String, partout, gagne la méthode — y compris ceux que manipulent d'autres bibliothèques. La portée maximale, donc le prix maximal.Reopening a class acts on the whole program at once: every String, everywhere, gains the method — including those other libraries handle. Maximum reach, hence maximum price.
« Ce patch doit-il être visible partout, ou seulement dans mon fichier ? » Si la réponse honnête est « mon fichier », un monkey-patch global est le mauvais outil : il impose à tout le programme un changement que toi seul voulais. Le refinement, lui, le garde local."Does this patch need to be visible everywhere, or only in my file?" If the honest answer is "my file", a global monkey-patch is the wrong tool: it forces on the whole program a change only you wanted. The refinement keeps it local.
On clôt la métaprog par son outil le plus tranchant. Rouvrir une classe du cœur paraît pratique — ajouter blank? à String, to_slug, un helper bien à soi. Mais l'action porte à distance, sur du code qu'on n'a pas écrit. Tout le numéro tient dans une question de portée : global (monkey-patch) ou local (refinement) — et, souvent, pas de patch du tout.We close metaprogramming with its sharpest tool. Reopening a core class looks handy — adding blank? to String, to_slug, a helper of your own. But the action reaches at a distance, into code you didn't write. The whole issue is a question of scope: global (monkey-patch) or local (refinement) — and, often, no patch at all.