extend self in ruby

Le code suivant a été testé avec ruby 1.9.3 .

La classe est destinée à la fois aux données et au comportement

Regardons ce code ruby.

1classe Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8

Ici nous avons une classe Util. Mais remarquez que toutes les méthodes de cette classe sont des méthodes de classe. Cette classe ne possède pas de variables d’instance. Habituellement, une classe est utilisée pour porter à la fois les données et le comportement et ,dans ce cas, la classe Util n’a que le comportement et aucune donnée.

Des outils utilitaires similaires en ruby

Maintenant, pour avoir une certaine perspective sur cette discussion, regardons quelques méthodes ruby qui font une chose similaire. En voici quelques-unes.

1require 'base64'2Base64.encode64('hello world') #=> "aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2

Dans tous les cas ci-dessus, la méthode de classe est invoquée sans créer une instance au préalable. C’est donc similaire à la façon dont j’ai utilisé Util.double .

Mais voyons quelle est la classe de tous ces objets.

1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module

Ce ne sont donc pas des classes mais des modules. Cela pose la question de savoir pourquoi les gars intelligents de ruby-core les ont implémentés comme modules au lieu de créer une classe comme je l’ai fait pour Util.

La raison est que la classe est trop lourde pour créer seulement des méthodes comme double. Comme nous l’avons discuté plus tôt, une classe est censée avoir à la fois des données et un comportement. Si la seule chose dont vous vous souciez est le comportement, alors ruby suggère de l’implémenter en tant que module.

extend self is the answer

Avant de continuer à discuter de extend self voici à quoi ressemblera ma classe Util après être passée de Class à Module.

1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8

Alors comment fonctionne extend self

D’abord voyons ce que fait extend.

1module M2 def double(i)3 i * 24 end5end67classe Calculator8 extend M9end10puts Calculator.double(4)

Dans le cas ci-dessus Calculator étend le module M et donc toutes les méthodes d’instance du module M sont directement disponibles pour Calculator.

Dans ce cas Calculator est une classe qui a étendu le module M. Cependant Calculator n’a pas besoin d’être une classe pour étendre un module.

Essayons maintenant une variante où Calculator est un module.

1module M2 def double(i)3 i * 24 end5end67module Calculator8 extend M9end10puts Calculator.double(4) #=> 8

Ici Calculator est un module qui étend un autre module.

Maintenant que nous comprenons qu’un module peut étendre un autre module, regardez le code ci-dessus et demandez pourquoi le module M est même nécessaire. Pourquoi ne pouvons-nous pas déplacer la méthode double vers le module Calculator directement. Essayons cela.

1module Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8

Je me suis débarrassé du module M et j’ai déplacé la méthode double à l’intérieur du module Calculator. Depuis que le module M a disparu, j’ai changé de extend M à extend Calculator.

Un dernier correctif.

Dans le module Calculator, qu’est-ce que self. self est le module Calculator lui-même. Il n’y a donc pas besoin de répéter Calculator deux fois. Voici la version finale

1module Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8

Conversion d’une classe en module

Chaque fois que je rencontrerais un code comme extend self, mon cerveau s’arrêterait un instant. Puis je le chercherais sur Google. Je l’ai lu. Trois mois plus tard, je répéterai tout le processus.

La meilleure façon de l’apprendre est de l’utiliser. J’ai donc commencé à chercher un cas pour utiliser extend self. Ce n’est pas une bonne pratique d’aller à la chasse au code pour appliquer une idée que vous avez dans votre esprit mais ici j’essayais d’apprendre.

Voici un instantané avant des méthodes de la classe Util que j’ai utilisé dans un projet.

1classe Util2 def self.config2hash(file) ; end3 def self.in_cents(amount) ; end4 def self.localhost2public_url(url, protocol) ; end5end

Après avoir utilisé extend self le code est devenu

1module Util2 extend self34 def config2hash(file) ; end5 def in_cents(amount) ; end6 def localhost2public_url(url, protocol) ; end7end

Bien mieux. Il rend l’intention claire et ,je crois, il est en ligne avec la façon dont ruby s’attendrait à ce que nous utilisions.

Un autre usage en ligne avec la façon dont Rails utilise extend self

Ici, je construis une application de commerce électronique et chaque nouvelle commande doit obtenir un nouveau numéro de commande à partir d’une application de vente tierce. Le code pourrait ressembler à ceci. J’ai omis l’implémentation des méthodes car elles ne sont pas pertinentes pour cette discussion.

1class Order2 def amount ; end3 def buyer ; end4 def shipped_at ; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number ; 'A100' ; end10end1112puts Order.new.number #=> A100

Ici la méthode next_order_number pourrait faire un appel compliqué à un autre système de vente. Idéalement, la classe Order ne devrait pas exposer la méthode next_order_number . Nous pouvons donc rendre cette méthode private mais cela ne résout pas le problème de fond. Le problème est que le modèle Order ne devrait pas savoir comment le nouveau numéro de commande est généré. Eh bien nous pouvons déplacer la méthode next_order_number vers une autre classe Util mais cela créerait trop de distance.

Voici une solution utilisant extend self.

1module Checkout2 extend self34 def next_order_number ; 'A100' ; end56 class Order7 def amount ; end8 def buyer ; end9 def shipped_at ; end10 def number11 @number || Checkout.next_order_number12 end13 end14end1516puts Checkout::Order.new.number #=> A100

Bien mieux. La classe Order n’expose pas la méthode next_order_number et cette méthode est juste là dans le même fichier. Pas besoin d’ouvrir la classe Util.

Articles

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.