Retour au cours

attributs et accesseurs : attr_reader, attr_writer, attr_accessor

Objectifs

  • Comprendre le principe d’encapsulation et pourquoi les variables d’instance sont privées.
  • Savoir créer des méthodes “getter” et “setter” manuellement pour contrôler l’accès.
  • Utiliser les raccourcis attr_reader, attr_writer, et attr_accessor pour générer automatiquement ces méthodes.

L’encapsulation : Protéger l’état interne

Un principe fondamental de la programmation orientée objet est l’encapsulation. Cela signifie que l’état interne d’un objet (ses variables d’instance @...) doit être considéré comme privé. On ne peut pas y accéder directement depuis l’extérieur de l’objet.

class Personne
  def initialize(nom)
    @nom = nom
  end
end

personne = Personne.new("Alice")
# personne.@nom # -> Erreur ! On ne peut pas accéder directement à @nom.

Pour permettre un accès contrôlé, on crée des méthodes publiques spécifiques.

Getters et Setters : Les portiers de vos données

Le Getter : Pour lire une donnée

Un “getter” est une méthode publique dont le seul rôle est de retourner la valeur d’une variable d’instance. Par convention, elle a le même nom que la variable (sans le @).

class Personne
  def initialize(nom)
    @nom = nom
  end

  # Getter manuel
  def nom
    @nom
  end
end

personne = Personne.new("Alice")
puts personne.nom # => "Alice"

Le Setter : Pour modifier une donnée

Un “setter” est une méthode qui permet de modifier la valeur d’une variable d’instance. Par convention, son nom se termine par =.

class Personne
  # ... (initialize et getter) ...

  # Setter manuel
  def nom=(nouveau_nom)
    @nom = nouveau_nom
  end
end

personne = Personne.new("Alice")
personne.nom = "Alicia" # Syntaxe spéciale qui appelle la méthode nom=
puts personne.nom      # => "Alicia"

Les accesseurs : La voie rapide de Ruby

Écrire ces méthodes manuellement est répétitif. Ruby fournit des “macros” (des méthodes qui écrivent du code pour vous) pour les générer automatiquement. On les appelle des accesseurs.

attr_reader : Accès en lecture seule

attr_reader crée automatiquement un “getter” pour vous.

class Personne
  attr_reader :nom # Crée la méthode 'nom' pour lire @nom

  def initialize(nom)
    @nom = nom
  end
end

personne = Personne.new("Alice")
puts personne.nom # => "Alice"
# personne.nom = "Alicia" # -> Erreur ! Pas de setter.

attr_writer : Accès en écriture seule

attr_writer crée automatiquement un “setter”. C’est moins courant.

class Personne
  attr_writer :nom # Crée la méthode 'nom=' pour écrire dans @nom
  
  # ... (on aurait aussi besoin d'un reader pour afficher le nom)
end

attr_accessor : Lecture ET écriture

C’est le plus utilisé. attr_accessor est un raccourci qui crée à la fois le getter et le setter.

class Personne
  # Raccourci pour attr_reader et attr_writer
  attr_accessor :nom

  def initialize(nom)
    @nom = nom
  end
end

personne = Personne.new("Alice")
puts personne.nom # => "Alice"
personne.nom = "Alicia"
puts personne.nom # => "Alicia"

On peut déclarer plusieurs accesseurs sur une même ligne : attr_accessor :nom, :age, :ville.

Bonnes pratiques

  • Utilisez les accesseurs attr_* dès que possible. C’est plus concis et plus clair.
  • N’écrivez un getter/setter manuel que si vous avez besoin d’une logique supplémentaire. Par exemple, un setter qui valide des données avant de les assigner.
  • Appliquez le principe du moindre privilège. Si un attribut n’a pas besoin d’être modifié de l’extérieur, n’utilisez qu’un attr_reader. N’exposez pas plus que ce qui est nécessaire.

Exercices

  1. Classe Livre :

    • Créez une classe Livre.
    • Le initialize doit prendre un titre et un auteur.
    • Le titre doit être lisible et modifiable de l’extérieur.
    • L’auteur doit être seulement lisible.
    • Utilisez les accesseurs appropriés (attr_accessor, attr_reader).
  2. Tester la classe Livre :

    • Créez une instance de votre classe Livre.
    • Affichez son titre et son auteur.
    • Modifiez son titre.
    • Essayez de modifier son auteur et observez l’erreur.
  3. Setter personnalisé :

    • Créez une classe Produit avec un attr_reader pour :prix.
    • Écrivez un setter manuel prix= qui refuse d’assigner la nouvelle valeur si elle est négative.
      def prix=(nouveau_prix)
        if nouveau_prix < 0
          puts "Erreur : Le prix ne peut pas être négatif."
        else
          @prix = nouveau_prix
        end
      end
    • Testez votre setter en essayant d’assigner un prix positif, puis un prix négatif.