Aller au contenu principal
Voir
Chapitre 8 / 16Les classes et les objets
1. Les variables et les types de données
2. Les chaînes et les symboles
3. Les tableaux
4. Les tables associatives
5. Les conditions
6. Les boucles et les itérateurs
7. Les méthodes et les blocs
8. Les classes et les objets
9. L'héritage
10. Les modules
11. Étendre du code avec prepend
12. Enumerable
13. Pour aller plus loin
14. La gestion des erreurs
15. Lire et écrire des fichiers
16. Les fonctionnalités avancées des classes

Les classes et les objets

Ce chapitre introduit les classes, le mécanisme central pour organiser le code en Ruby. Une classe regroupe des données et des comportements dans un seul endroit.

Principe

Jusqu'ici, on a utilisé des Hash pour représenter un Pokémon : { name: 'Pikachu', level: 25 }. Ça fonctionne, mais ça a des limites :

  • On ne peut pas contrôler les valeurs (rien n'empêche de mettre un niveau négatif)
  • On ne peut pas attacher de comportement au Hash (pas de méthode heal ou take_damage)
  • On peut faire des fautes de frappe dans les clés sans que Ruby ne prévienne

Une classe résout tout cela. C'est un plan de construction qui définit :

  • Quelles données l'objet contient (variables d'instance @)
  • Quels comportements il a (méthodes)

Un objet est une instance créée à partir de ce plan.

Définir une classe

class Pokemon
def initialize(name, level)
@name = name
@level = level
end
end

pikachu = Pokemon.new('Pikachu', 25)
p pikachu # => #<Pokemon @name="Pikachu", @level=25>
  • class Pokemon ... end définit la classe. Le nom commence par une majuscule (convention CamelCase).
  • initialize est une méthode spéciale appelée automatiquement par .new. C'est le constructeur : il reçoit les arguments et initialise les variables d'instance.
  • @name et @level sont des variables d'instance. Elles appartiennent à l'objet et persistent entre les appels de méthodes.
  • Pokemon.new('Pikachu', 25) crée un nouvel objet et appelle initialize avec ces arguments.

Variables d'instance et accesseurs

Les variables d'instance (@name, @level) sont privées par défaut. On ne peut pas y accéder depuis l'extérieur :

pikachu = Pokemon.new('Pikachu', 25)
# pikachu.name # => Erreur ! NoMethodError

Pour autoriser la lecture, on utilise attr_reader :

class Pokemon
attr_reader :name, :level

def initialize(name, level)
@name = name
@level = level
end
end

pikachu = Pokemon.new('Pikachu', 25)
puts pikachu.name # => Pikachu
puts pikachu.level # => 25
# pikachu.level = 50 # => Erreur ! Pas de setter
  • attr_reader :name, :level crée des méthodes de lecture (getters) pour @name et @level.

Il existe aussi :

  • attr_writer :level : crée un setter uniquement (pikachu.level = 50)
  • attr_accessor :level : crée getter et setter

Mais attr_accessor ne fait aucune vérification. Si on veut contrôler les valeurs, on écrit un setter personnalisé.

Setters personnalisés

Un setter personnalisé est une méthode dont le nom se termine par =. Ruby traduit pikachu.level = 50 en appel de cette méthode :

  • def level=(new_level) : le = fait partie du nom de la méthode. C'est ce qui permet d'écrire pokemon.level = 50.
  • .clamp(1, 100) borne la valeur entre 1 et 100.
  • self.level = level dans initialize appelle le setter (et donc la validation). Si on écrivait @level = level, on contournerait la validation.

Méthodes d'instance

Les méthodes définies dans une classe sont des méthodes d'instance. Elles opèrent sur les données de l'objet :

  • Chaque méthode accède aux variables d'instance de l'objet sur lequel elle est appelée.
  • fainted? se termine par ? : convention Ruby pour les méthodes qui retournent un booléen.

self — l'objet courant

self fait référence à l'objet courant. Dans une méthode d'instance, self est l'objet sur lequel la méthode a été appelée :

class Pokemon
attr_reader :name

def initialize(name)
@name = name
end

def introduce
puts "Je suis #{self.name} !"
# Équivalent à :
puts "Je suis #{name} !"
# Équivalent à :
puts "Je suis #{@name} !"
end
end
  • self.name, name et @name accèdent tous à la même donnée ici. self. est optionnel pour les getters.
  • self est obligatoire pour appeler un setter : self.level = 50. Sans self., Ruby penserait qu'on crée une variable locale.

Méthodes de classe

Une méthode de classe est appelée sur la classe elle-même, pas sur un objet. On la définit avec def self. :

class Pokemon
attr_reader :name, :level

def initialize(name, level)
@name = name
@level = level
end

# Méthode de classe : crée un starter au niveau 5
def self.starter(name)
return Pokemon.new(name, 5)
end
end

pikachu = Pokemon.starter('Pikachu')
puts pikachu.level # => 5
  • def self.starter est appelée via Pokemon.starter(...), pas via un objet.
  • C'est un pattern de fabrique : une méthode qui crée des objets avec une configuration prédéfinie.

Pour regrouper plusieurs méthodes de classe, on peut utiliser class << self :

class Pokemon
class << self
def starter(name)
return Pokemon.new(name, 5)
end

def from_hash(data)
return Pokemon.new(data[:name], data[:level])
end
end
end

Visibilité : public, private, protected

Par défaut, toutes les méthodes sont publiques. On peut restreindre l'accès :

class Pokemon
attr_reader :name

def initialize(name, level)
@name = name
@level = level
end

# Publique : accessible de l'extérieur
def summary
puts "#{@name} Niv.#{@level}"
puts " PV max : #{calculate_max_hp}"
end

private

# Privée : accessible uniquement depuis l'intérieur de la classe
def calculate_max_hp
return @level * 3 + 10
end
end

pikachu = Pokemon.new('Pikachu', 25)
pikachu.summary # Fonctionne
# pikachu.calculate_max_hp # => Erreur ! NoMethodError
  • private : tout ce qui est en dessous de ce mot-clé devient privé. Ces méthodes ne sont accessibles que depuis l'intérieur de la classe.
  • protected : comme private, mais les méthodes sont accessibles entre instances de la même classe. Utile pour les comparaisons entre objets.

to_s — affichage lisible

to_s est une méthode spéciale : Ruby l'appelle automatiquement quand on utilise puts ou l'interpolation #{} :

class Pokemon
def initialize(name, level)
@name = name
@level = level
end

def to_s
return "#{@name} Niv.#{@level}"
end
end

pikachu = Pokemon.new('Pikachu', 25)
puts pikachu # => Pikachu Niv.25
puts "Go, #{pikachu} !" # => Go, Pikachu Niv.25 !
  • Sans to_s, puts pikachu afficherait quelque chose comme #<Pokemon:0x00007f...>, ce qui n'est pas très utile.

Constantes dans une classe

class Pokemon
MAX_LEVEL = 100

def initialize(name, level)
@name = name
@level = level.clamp(1, MAX_LEVEL)
end
end

puts Pokemon::MAX_LEVEL # => 100
  • Les constantes définies dans une classe sont accessibles avec :: depuis l'extérieur.
  • À l'intérieur de la classe, on y accède directement par leur nom.

Conclusion

  • class ClassName ... end définit une classe. .new crée un objet et appelle initialize.
  • Les variables d'instance (@var) stockent les données de l'objet. Elles sont privées par défaut.
  • attr_reader crée des getters. attr_accessor crée getters + setters (sans validation).
  • Les setters personnalisés (def level=(value)) permettent de valider les données.
  • self est l'objet courant. Obligatoire pour appeler un setter (self.level = 50).
  • def self.method_name définit une méthode de classe (appelée sur la classe, pas sur un objet).
  • private rend les méthodes accessibles uniquement à l'intérieur de la classe.
  • to_s définit l'affichage lisible d'un objet pour puts et l'interpolation.