Retour au cours

les modules : mixins et namespaces

Objectifs

  • Comprendre les deux rôles principaux d’un module en Ruby.
  • Utiliser un module comme namespace pour organiser son code et éviter les conflits de noms.
  • Utiliser un module comme mixin pour partager des fonctionnalités communes entre plusieurs classes.

Un module est une collection de méthodes, de constantes et d’autres modules ou classes. On ne peut pas créer d’instance d’un module (pas de MonModule.new). Ils servent à deux choses principales.

1. Utilisation comme “Namespace” (Espace de nommage)

C’est l’usage le plus simple. Un module peut servir de “boîte” ou de “dossier” pour regrouper des classes et des méthodes qui ont un lien logique. Cela permet d’éviter les conflits si deux classes ont le même nom dans des parties différentes d’une grosse application.

module ECommerce
  class Commande
    # ... logique de commande
  end
end

module SupportClient
  class Commande
    # ... logique de commande de support, complètement différente
  end
end

# Pour utiliser ces classes, on les préfixe avec le nom du module
nouvelle_commande = ECommerce::Commande.new
ticket_support = SupportClient::Commande.new

L’opérateur :: est l’opérateur de résolution de portée.

2. Utilisation comme “Mixin” (Partage de comportement)

C’est l’usage le plus puissant et le plus idiomatique de Ruby. Un mixin permet d’injecter des méthodes d’un module directement dans une classe. C’est la solution de Ruby pour le partage de code, qui est souvent préférée à l’héritage multiple (qui n’existe pas en Ruby).

On utilise le mot-clé include dans une classe pour “mixer” un module. Toutes les méthodes définies dans le module deviennent alors des méthodes d’instance de la classe.

Exemple de Mixin

Imaginons que nous ayons plusieurs classes qui ont besoin d’une fonctionnalité de “déplacement”.

# On définit le comportement dans un module
module Deplacable
  def avancer
    puts "J'avance."
  end
  
  def reculer
    puts "Je recule."
  end
end

# Maintenant, on peut inclure ce module dans n'importe quelle classe
class Voiture
  include Deplacable
  # La classe Voiture a maintenant les méthodes 'avancer' et 'reculer'
end

class Robot
  include Deplacable
  # Le Robot aussi !
end

ma_voiture = Voiture.new
mon_robot = Robot.new

ma_voiture.avancer # => "J'avance."
mon_robot.reculer  # => "Je recule."

Cette approche est extrêmement flexible. Une classe peut inclure plusieurs modules pour hériter de plusieurs comportements différents.

include vs extend

  • include MonModule : Les méthodes du module deviennent des méthodes d’instance. C’est ce que vous utiliserez le plus souvent.
  • extend MonModule : Les méthodes du module deviennent des méthodes de classe.
module MonHelper
  def methode_utile
    "Ceci est utile"
  end
end

class MaClasse
  extend MonHelper
end

# La méthode est sur la classe, pas sur l'instance
MaClasse.methode_utile # => "Ceci est utile"

Bonnes pratiques

  • Utilisez des modules pour regrouper des fonctionnalités qui ont un lien sémantique (ex: Paiement, Authentification).
  • Utilisez les mixins pour partager des comportements transverses qui ne sont pas liés par une relation “est un” (héritage). Par exemple, un module Loggable qui fournit une méthode log peut être inclus dans n’importe quelle classe qui a besoin d’écrire des logs.

Exercices

  1. Module de calcul :

    • Créez un module MathUtils qui contient deux méthodes : doubler(nombre) et tripler(nombre).
    • Créez une classe Calculatrice qui n’a pas de méthodes propres.
    • Incluez le module dans la classe.
    • Créez une instance de Calculatrice et appelez les méthodes doubler et tripler.
  2. Mixin de communication :

    • Créez un module Communication avec une méthode dire_bonjour et une méthode dire_au_revoir.
    • Créez deux classes, Humain et AnimalDeCompagnie.
    • Faites en sorte que la classe Humain puisse utiliser les deux méthodes.
    • Faites en sorte que la classe AnimalDeCompagnie ne puisse utiliser que la méthode dire_bonjour. (Indice : vous pouvez définir un autre module qui n’inclut qu’une partie du premier).
  3. Namespace :

    • Créez un module Inventaire contenant une classe Produit.
    • Créez un module Marketing contenant également une classe Produit.
    • Instanciez un objet de chaque classe pour montrer qu’il n’y a pas de conflit.