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
healoutake_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 ... enddéfinit la classe. Le nom commence par une majuscule (conventionCamelCase).initializeest 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.@nameet@levelsont 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 appelleinitializeavec 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, :levelcrée des méthodes de lecture (getters) pour@nameet@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'écrirepokemon.level = 50..clamp(1, 100)borne la valeur entre 1 et 100.self.level = leveldansinitializeappelle 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,nameet@nameaccèdent tous à la même donnée ici.self.est optionnel pour les getters.selfest obligatoire pour appeler un setter :self.level = 50. Sansself., 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.starterest appelée viaPokemon.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: commeprivate, 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 pikachuafficherait 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 ... enddéfinit une classe..newcrée un objet et appelleinitialize.- Les variables d'instance (
@var) stockent les données de l'objet. Elles sont privées par défaut. attr_readercrée des getters.attr_accessorcrée getters + setters (sans validation).- Les setters personnalisés (
def level=(value)) permettent de valider les données. selfest l'objet courant. Obligatoire pour appeler un setter (self.level = 50).def self.method_namedéfinit une méthode de classe (appelée sur la classe, pas sur un objet).privaterend les méthodes accessibles uniquement à l'intérieur de la classe.to_sdéfinit l'affichage lisible d'un objet pourputset l'interpolation.