24 Jan 2016
Dans le langage de programmation Ruby, les méthodes définies se déclinent en deux variantes : les méthodes d’instance et les méthodes de classe. Les méthodes d’instance sont disponibles après l’initialisation d’un objet, créant une instance. Les méthodes de classe, en revanche, sont disponibles sans créer d’instance de la classe sur laquelle elles sont définies.
La visibilité des méthodes Ruby peut varier. Les méthodes publiques sont disponibles dans n’importe quel contexte, tandis que la disponibilité des méthodes privées est limitée dans l’instance d’une classe et de ses descendants. Une troisième portée de visibilité, protected, se comporte de la même manière que les méthodes privées, mais les méthodes protégées peuvent être appelées par d’autres instances de la même classe.
Pour une mise à jour rapide, les méthodes d’instance publique et privée ressemblent à ceci:
class Dog def do_trick bark end private def bark puts 'woof woof' endend
Lorsque la méthode publique est appelée:
> dog = Dog.new> dog.do_trick# => woof woof
Et la méthode privée:
> dog = Dog.new> dog.bark# => NoMethodError: private method `bark' called for <Dog>
Les méthodes de classe privée peuvent ne pas être aussi courantes que les méthodes d’instance privée, mais elles ont toujours leur place. Par exemple, une méthode de classe peut nécessiter des méthodes d’assistance internes pour compléter sa fonction. Quelle que soit la raison, la définition de méthodes de classe privée a de la valeur mais n’est pas toujours intuitive.
Cet exemple de classe Dog
doit conserver une liste de tricks
qui seront utilisées dans les autres méthodes de classe publique. Cette liste ne doit pas être accessible aux appelants en dehors de la classe Dog
.
Dans le mauvais sens
Une première passe à l’écriture de la méthode privée tricks
pourrait ressembler à:
class Dog private def self.tricks endend
Cependant, lors du test de la visibilité de la méthode tricks
:
> Dog.tricks# =>
Euh oh, aucune erreur n’a été lancée indiquant qu’une méthode privée a été appelée, cette méthode est complètement publique.
Pourquoi?
La raison pour laquelle le code ci-dessus n’a pas produit de méthode privée est liée à la hiérarchie d’objets de Ruby, aux interactions entre les classes internes, aux instances de ces classes et aux classes propres. Un article détaillé sur le fonctionnement des objets Ruby les uns avec les autres se trouve dans un article précédent.
Lors de la définition de méthodes dans une classe, le contexte par défaut et le contexte introduit par la déclaration de méthode self.
sont distincts.
La portée de la méthode privée ne peut pas être utilisée de la manière ci-dessus car elle ne gère pas le changement de contexte entre les méthodes définies sur la classe Dog
et définies dans le contexte self
.
Alors quelles sont les alternatives?
class < < self
Une autre façon de définir des méthodes de classe sur un objet consiste à utiliser la syntaxe class << self
. Cette syntaxe ouvre une classe propre pour l’argument fourni. Dans cet exemple, self
dans la classe Dog
ouvrira la classe propre Dog's
.
class Dog class << self private def tricks end endend
Une chose à noter est que lors de la définition de méthodes comme celle-ci, la déclaration est passée de def self.tricks
à simplement def tricks
.
Maintenant, la portée privée est préservée et le comportement attendu est atteint:
> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Modules
Les modules de ruby sont des collections de méthodes et de constantes. Ces collections peuvent être utilisées comme outils d’encapsulation ou, dans ce cas, comme alternatives à la définition de méthodes de classes publiques et privées.
Lorsqu’une classe extends
un module, toutes les méthodes de ce module deviennent des méthodes de classe sur la classe de sujet *. Ce modèle peut être utilisé pour définir la méthode tricks
sur Dog
.
* Remarque: Cela ne concerne que les méthodes définies dans un « sens typique », les définitions de méthodes def self.
ou def Dog.
dans un module ne deviennent pas automatiquement des méthodes de classe de la même manière lorsque extended
.
class Dog module ClassMethods private def tricks end end extend ClassMethodsend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Un avantage de cette approche est la lisibilité. Le module nommé ClassMethods
, qui est contenu et donc encapsulé par la classe Dog
, est la maison claire pour toutes les méthodes de classe souhaitées.
Les modificateurs de visibilité comme private
se comportent en conséquence et un simple extend
au bas de la définition du module rassemble tout cela.
private_class_method
Une troisième approche consiste à utiliser la méthode intégrée Module#private_class_method
. Le but de cette méthode est de modifier la visibilité des méthodes de classe existantes.
Contrairement aux deux autres solutions, celle-ci ne nécessite pas un style de définition de méthode différent de la solution incorrecte:
class Dog def self.tricks end private_class_method :tricksend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Cette méthode peut également être intégrée lors de la définition de la méthode de la classe:
class Dog private_class_method def self.tricks endend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Si une méthode de classe unique doit être privée et que l’enregistrement de lignes est très important, ce style peut être applicable; mais, il sacrifie certainement un niveau de lisibilité.
Une force majeure de private_class_method
est son caractère explicite. Contrairement aux autres approches, celle-ci ne nécessite pas de définition de module ou de commutation de contexte de définition de méthode spéciale.
Lectures supplémentaires
Les solutions susmentionnées feront le travail, mais pourraient ne pas répondre aux questions persistantes sur les raisons pour lesquelles les choses fonctionnent comme elles le font. Quelques excellents articles sur la classe propre ruby et les réflexions de Matz sur la conception de la méthode Ruby devraient aider à brosser un tableau plus concis des raisons pour lesquelles la définition de la méthode de classe privée de Ruby est complexe.