Banque de cours - Cours 10 - Gym Colorlab v3 orienté objet
17 min de lecture
# Cours 10 - Gym Colorlab v3 : orienté objet
ColorLab v3 - La Classe Color
Prérequis : Vous devez avoir complété le ColorLab v2 (semaine 5). Nous allons transformer le struct Color en une véritable class Color avec encapsulation.
Rappel - Ce qu’on avait en v1/v2
Dans les semaines 4 et 5, notre Color était un simple struct (introduit en semaine 4, enrichi avec des palettes et des opérations sur les chaînes en semaine 5) :
// v1/v2 - struct avec membres publicsstruct Color { std::string name; int r {}; int g {}; int b {};};// Fonctions libres qui opèrent sur Colorvoid displayColor(const Color& color);int brightness(const Color& color);Color mixColors(const Color& a, const Color& b, const std::string& mixName);
N’importe qui pouvait écrire soleil.r = -500; et casser notre couleur. Les fonctions comme displayColor et brightness étaient séparées du type - elles auraient dû faire partie de Color lui-même.
L'évolution - Pourquoi une classe ?
Trois raisons majeures justifient le passage de struct à class :
Encapsulation - Les composantes R, G, B sont maintenant private. On ne peut plus les corrompre de l’extérieur. Toute modification passe par des méthodes contrôlées.
Validation dans le constructeur - Le clamping (0–255) est intégré directement dans le constructeur. Impossible de créer une couleur invalide.
Comportement attaché aux données - display(), brightness() et mix() deviennent des méthodes de la classe. On écrit soleil.display() au lieu de displayColor(soleil) - plus naturel et orienté objet.
défi : Modifiez colors.h, colors.cpp et main.cpp de manière à ce qu’une classe nommée Color soit utilisée
colors.h déclare la classe Color (prototype des méthodes publiques et privées, getters, setters, les variables privées, les constructeur, les valeurs par défaut, etc.
colors.cpp définit les méthodes de la classe Color, via le scope resolution operator ::, par exemple :
Dans colors.h :class Color{public: ... // Calcule la luminosité moyenne pondérée (BT.709). // const - ne modifie pas l'objet. int brightness() const; ...}Dans colors.cpp :// ------------------------------------------------------------// brightness - luminosité moyenne pondérée// ITU-R BT.709 luma : Y = 0.2126·R + 0.7152·G + 0.0722·B// const - ne modifie pas l'objet// ------------------------------------------------------------int Color::brightness() const { // Ici, le compilateur sait que la méthode brightness que l'on définit ici appartient à la class Color grâce au scope resolution operator :: return 0.2126 * m_r + 0.7152 * m_g + 0.0722 * m_b;}
Modifiez main.cpp également bien sûr. La sortie attendue devrait être la même que la v2 de Colorlab :
Solution
1 - Le Header File (colors.h) - v3
// colors.h - v3// ==============// Déclarations pour le ColorLab v3 : class Color avec encapsulation.#ifndef COLORS_H#define COLORS_H#include <string>#include <vector>// --- Notre type Color est maintenant une classe ---class Color{public: // Constructeur paramétré - clampe automatiquement les composantes dans [0, 255]. // name est passé BY CONST REFERENCE - lecture seule, pas de copie inutile. // r, g, b sont passés BY VALUE - ce sont de petits int, copie triviale. Color(const std::string& name, int r, int g, int b); // Constructeur par défaut - crée une couleur noire nommée "Default". Color(); // --- Getters (const - ils ne modifient pas l'objet) --- // Retourne le nom de la couleur. // BY VALUE pour std::string - retourne une copie. std::string name() const; // Retourne la composante rouge (0-255). int r() const; // Retourne la composante verte (0-255). int g() const; // Retourne la composante bleue (0-255). int b() const; // --- Méthodes (comportement attaché à l'objet) --- // Affiche la couleur avec un bloc ANSI coloré dans le terminal. // const - ne modifie pas l'objet. void display() const; // Calcule la luminosité moyenne pondérée (BT.709). // const - ne modifie pas l'objet. int brightness() const; // Mélange cette couleur avec une autre et retourne le résultat. // other est passé BY CONST REFERENCE - on ne modifie pas l'autre couleur. // mixName est passé BY CONST REFERENCE - lecture seule. // const - ne modifie pas l'objet courant non plus. Color mix(const Color& other, const std::string& mixName) const; // --- Membre static : compteur d'instances --- // Retourne le nombre total d'objets Color créés. // static - pas besoin d'un objet pour l'appeler. static int getCount();private: std::string m_name; // Nom de la couleur int m_r {}; // Rouge (0-255) int m_g {}; // Vert (0-255) int m_b {}; // Bleu (0-255) static int s_count; // Compteur d'instances partagé par tous les objets Color // Garde une valeur dans la plage [0, 255]. // static car elle n'a pas besoin d'accéder aux membres d'une instance. static int clamp(int value);};// --- Fonctions libres (opèrent sur des collections, pas sur une seule couleur) ---// Affiche toutes les couleurs d'une palette.// Le vector est passé BY CONST REFERENCE - on ne modifie pas la palette.void displayPalette(const std::vector<Color>& palette);// Cherche une couleur par nom dans la palette.// Retourne l'index si trouvée, -1 sinon.int findByName(const std::vector<Color>& palette, const std::string& name);// Retourne une palette de couleurs web standard prédéfinies.std::vector<Color> getWebColors();#endif // COLORS_H
Ce qui a changé par rapport à v2
Aspect
v2 (semaine 5)
v3 (semaine 6)
Type
struct Color (tout public)
class Color (private par défaut)
Accès aux données
Direct : color.r
Via getters : color.r()
Validation
createColor() - fonction libre
Constructeur avec clamping intégré
displayColor(c)
Fonction libre
Méthode : c.display()
brightness(c)
Fonction libre
Méthode : c.brightness()
mixColors(a, b, nom)
Fonction libre
Méthode : a.mix(b, nom)
clamp()
Fonction libre publique
Méthode statique privée
Compteur d’instances
Non disponible
Color::getCount() via static
2 - Le Source File (colors.cpp) - v3
// colors.cpp - v3// ================// Définitions pour le ColorLab v3.#include "colors.h"#include <iostream>#include <format>// ------------------------------------------------------------// Initialisation du membre static - obligatoire en dehors de la classe !// ------------------------------------------------------------int Color::s_count {0};// ------------------------------------------------------------// clamp - méthode statique privée// Retourne la valeur bornée dans [0, 255].// static : pas besoin d'un objet, c'est un utilitaire de la classe.// ------------------------------------------------------------int Color::clamp(int value) { if (value < 0) return 0; if (value > 255) return 255; return value;}// ------------------------------------------------------------// Constructeur paramétré - liste d'initialisation + clamping// Les composantes sont clampées automatiquement dès la création.// name est BY CONST REFERENCE - on copie dans m_name via l'init list.// ------------------------------------------------------------Color::Color(const std::string& name, int r, int g, int b) : m_name{name}, m_r{clamp(r)}, m_g{clamp(g)}, m_b{clamp(b)}{ ++s_count;}// ------------------------------------------------------------// Constructeur par défaut - couleur noire "Default"// Utilise la liste d'initialisation pour tout initialiser.// ------------------------------------------------------------Color::Color() : m_name{"Default"}, m_r{0}, m_g{0}, m_b{0}{ ++s_count;}// ------------------------------------------------------------// Getters - const car ils ne modifient pas l'objet// ------------------------------------------------------------std::string Color::name() const { return m_name; }int Color::r() const { return m_r; }int Color::g() const { return m_g; }int Color::b() const { return m_b; }// ------------------------------------------------------------// display - v3 : méthode de la classe au lieu de fonction libre// const - ne modifie pas l'objet// ------------------------------------------------------------void Color::display() const { std::cout << std::format(" [{:<20s}] ", m_name); std::cout << "\033[38;2;" << m_r << ";" << m_g << ";" << m_b << "m"; std::cout << "████████"; std::cout << "\033[0m"; std::cout << std::format(" R:{:<3d} G:{:<3d} B:{:<3d}", m_r, m_g, m_b); std::cout << std::endl;}// ------------------------------------------------------------// brightness - luminosité moyenne pondérée// ITU-R BT.709 luma : Y = 0.2126·R + 0.7152·G + 0.0722·B// const - ne modifie pas l'objet// ------------------------------------------------------------int Color::brightness() const { return 0.2126 * m_r + 0.7152 * m_g + 0.0722 * m_b;}// ------------------------------------------------------------// mix - mélange avec une autre couleur// other est BY CONST REFERENCE - on ne modifie pas l'autre couleur.// mixName est BY CONST REFERENCE - on ne copie pas la string inutilement.// const - ne modifie ni l'objet courant ni l'autre.// Retourne un nouveau Color BY VALUE.// ------------------------------------------------------------Color Color::mix(const Color& other, const std::string& mixName) const { return Color{mixName, (m_r + other.m_r) / 2, (m_g + other.m_g) / 2, (m_b + other.m_b) / 2};}// ------------------------------------------------------------// getCount - membre static, pas besoin d'objet// ------------------------------------------------------------int Color::getCount() { return s_count;}// ============================================================// Fonctions libres - elles opèrent sur des vectors, pas sur// une seule couleur, donc elles restent en dehors de la classe.// ============================================================// ------------------------------------------------------------// displayPalette - affiche toutes les couleurs d'un vector// Le vector est passé BY CONST REFERENCE - efficace et sûr// ------------------------------------------------------------void displayPalette(const std::vector<Color>& palette) { std::cout << std::format("=== Palette ({} couleurs) ===", palette.size()) << std::endl; for (const Color& c : palette) { c.display(); // v3 : on appelle la méthode, pas une fonction libre }}// ------------------------------------------------------------// findByName - recherche linéaire dans la palette// Retourne l'index ou -1 si non trouvée// On utilise le getter name() au lieu d'accéder directement au membre// ------------------------------------------------------------int findByName(const std::vector<Color>& palette, const std::string& name) { for (std::size_t i {0}; i < palette.size(); ++i) { if (palette[i].name() == name) { return static_cast<int>(i); } } return -1;}// ------------------------------------------------------------// getWebColors - retourne une palette de couleurs web connues// Chaque couleur est maintenant créée via le constructeur avec clamping.// ------------------------------------------------------------std::vector<Color> getWebColors() { return { {"Teal", 0, 128, 128}, {"Salmon", 250, 128, 114}, {"Coral", 255, 127, 80}, {"Goldenrod", 218, 165, 32}, {"Orchid", 218, 112, 214}, {"SteelBlue", 70, 130, 180}, {"Tomato", 255, 99, 71}, {"MediumAquamarine", 102, 205, 170}, {"SlateGray", 112, 128, 144}, {"Crimson", 220, 20, 60}, {"LimeGreen", 50, 205, 50}, {"DarkOrange", 255, 140, 0} };}
Points importants à noter
**class Color** utilise l’encapsulation : les données sont private, l’accès passe par des getters const.
Le constructeur appelle clamp() dans la liste d’initialisation - impossible de créer une couleur invalide.
**clamp()**** est **static** et ****private** - c’est un utilitaire interne de la classe, invisible de l’extérieur.
**s_count** est initialisé en dehors de la classe (int Color::s_count {0};) - c’est obligatoire pour les membres static.
Les fonctions libres (displayPalette, findByName, getWebColors) restent en dehors de la classe car elles opèrent sur des collections, pas sur une seule couleur.
3 - main.cpp - v3
// main.cpp - ColorLab v3// =======================// Classes, encapsulation et méthodes.#include "colors.h"#include <iostream>#include <format>#ifdef _WIN32#include <windows.h>#endifint main() { // Sur Windows, forcer la console à interpréter l'UTF-8#ifdef _WIN32 SetConsoleOutputCP(CP_UTF8);#endif std::cout << "=== ColorLab v3 - La Classe Color ===" << "\n\n"; // --- Créer des couleurs avec le constructeur --- Color soleil("Rouge Soleil", 220, 60, 10); Color ocean("Bleu Océan", 10, 80, 210); std::cout << "--- Nos couleurs personnalisées ---" << std::endl; soleil.display(); // v3 : méthode au lieu de displayColor(soleil) ocean.display(); std::cout << std::endl; // --- Luminosité - maintenant c'est une méthode --- std::cout << std::format(" Luminosité de {} : {}/255", soleil.name(), soleil.brightness()) << "\n"; std::cout << std::format(" Luminosité de {} : {}/255", ocean.name(), ocean.brightness()) << "\n\n"; // --- Mélanger - méthode de l'objet --- Color fusion = soleil.mix(ocean, "Fusion"); std::cout << "--- Mélange ---" << std::endl; fusion.display(); std::cout << std::format(" Luminosité de {} : {}/255", fusion.name(), fusion.brightness()) << "\n\n"; // --- Compteur d'instances (static) --- std::cout << std::format("Nombre de Color créés jusqu'ici : {}", Color::getCount()) << "\n\n"; // --- Charger la palette de couleurs web --- std::vector<Color> palette = getWebColors(); displayPalette(palette); std::cout << std::endl; // --- Compteur mis à jour après la palette --- std::cout << std::format("Nombre total de Color créés : {}", Color::getCount()) << "\n\n"; // --- Rechercher une couleur par nom --- std::string recherche {"Coral"}; int index = findByName(palette, recherche); if (index >= 0) { std::cout << std::format("'{}' trouvé à l'index {} :", recherche, index) << "\n"; palette[index].display(); } else { std::cout << std::format("'{}' non trouvé dans la palette.", recherche) << "\n"; } std::cout << std::endl; // --- Tester le clamping du constructeur --- Color extreme {"Extreme", 300, -50, 999}; std::cout << "--- Test clamping constructeur ---" << std::endl; extreme.display(); // Doit afficher R:255 G:0 B:255 std::cout << std::endl; // --- Couleur par défaut --- Color defaut; std::cout << "--- Couleur par défaut ---" << std::endl; defaut.display(); // "Default" R:0 G:0 B:0 std::cout << std::endl; std::cout << std::format("Nombre final de Color créés : {}", Color::getCount()) << "\n"; std::cout << "\n=== Séance de Labo Terminée ===" << std::endl; return 0;}
Récapitulatif : qu'avons-nous appris ?
Concept
Où on l’a vu
**class**
class Color - encapsulation des données
**private**
m_name, m_r, m_g, m_b - inaccessibles de l’extérieur
Le clamping est interne, les données sont protégées
Erreurs courantes à surveiller
**1. Accéder directement aux membres ****private**
Color c {"Rouge", 255, 0, 0};std::cout << c.m_r; // ERREUR - m_r est private !std::cout << c.r(); // OK - on passe par le getter
2. Oublier **const** sur les getters et méthodes de lecture
int r() { return m_r; } // Oubli de const - ne compilera pas sur un objet const !int r() const { return m_r; } // OK - promet de ne pas modifier l'objet
3. Oublier d’initialiser le membre **static** en dehors de la classe
class Color { static int s_count; // Déclaration seulement !};// ERREUR si on oublie cette ligne :int Color::s_count {0}; // Définition obligatoire dans le .cpp
4. Appeler une méthode non-**const**** sur un objet ****const**
void afficher(const Color& c) { c.display(); // OK seulement si display() est const ! // Si display() n'était pas const, ERREUR de compilation ici.}
5. Ne pas utiliser la liste d’initialisation
// Mauvais - initialise par défaut puis assigne :Color::Color(const std::string& name, int r, int g, int b) { m_name = name; // double travail : init par défaut + assignation m_r = clamp(r);}// Bon - initialise directement :Color::Color(const std::string& name, int r, int g, int b) : m_name{name}, m_r{clamp(r)}, m_g{clamp(g)}, m_b{clamp(b)}{ ++s_count;}
Défis
Défi 1 - Surcharge d'opérateurs
Ajoutez deux opérateurs à la classe Color :
**operator==** - Compare deux couleurs par leurs composantes R, G, B (le nom n’est pas pris en compte).
**operator+** - Mélange deux couleurs en faisant la moyenne de leurs composantes et en combinant leurs noms avec " + ".
// Déclarations à ajouter dans la section public de Color (colors.h) :bool operator==(const Color& other) const;Color operator+(const Color& other) const;
Testez dans main.cpp :
Color a {"Rouge", 255, 0, 0};Color b {"Bleu", 0, 0, 255};if (a == b) { std::cout << "Identiques !\n"; }else { std::cout << "Différentes !\n"; }Color somme = a + b;somme.display(); // "Rouge + Bleu" R:127 G:0 B:127
Défi 2 - Setters avec validation
Ajoutez des setters qui démontrent la valeur de l'encapsulation :
// Déclarations à ajouter dans la section public de Color (colors.h) :void setR(int value);void setG(int value);void setB(int value);
Chaque setter doit clamper la valeur dans [0, 255] avant de l’assigner - exactement comme le constructeur. C’est l’avantage de l’encapsulation : on ne peut jamais mettre une valeur invalide !
Testez dans main.cpp :
Color c {"Test", 100, 100, 100};c.setR(999); // clampé à 255c.setG(-50); // clampé à 0c.display(); // R:255 G:0 B:100
Défi 3 - Couleur inversée (avancé)
Ajoutez une méthode qui retourne la couleur complémentaire (l'inverse) :
// Déclaration à ajouter dans la section public de Color (colors.h) :Color invert() const;
La couleur inversée se calcule comme : (255 - r, 255 - g, 255 - b). Le nom de la couleur inversée sera "Inv:" + m_name.
Testez en affichant des paires de couleurs avec leur inverse :
Color tomate {"Tomato", 255, 99, 71};Color inv = tomate.invert();std::cout << "--- Original et inverse ---\n";tomate.display(); // R:255 G:99 B:71inv.display(); // R:0 G:156 B:184
Solutions des défis
Solution - Défi 1
Déclarations dans **colors.h** (section **public** de **Color**) :
// Compare deux couleurs par composantes RGB (le nom est ignoré).// other est BY CONST REFERENCE - on ne modifie pas l'autre couleur.// const - ne modifie pas l'objet courant.bool operator==(const Color& other) const;// Mélange deux couleurs (moyenne des composantes, concaténation des noms).// other est BY CONST REFERENCE.// const - ne modifie pas l'objet courant.// Retourne un nouveau Color BY VALUE.Color operator+(const Color& other) const;
Définitions dans **colors.cpp** :
// ------------------------------------------------------------// operator== - compare les composantes RGB uniquement// Le nom est intentionnellement ignoré : deux couleurs avec// des noms différents mais les mêmes composantes sont "égales".// ------------------------------------------------------------bool Color::operator==(const Color& other) const { return m_r == other.m_r && m_g == other.m_g && m_b == other.m_b;}// ------------------------------------------------------------// operator+ - mélange deux couleurs par moyenne// Retourne un nouveau Color avec le nom combiné// ------------------------------------------------------------Color Color::operator+(const Color& other) const { return Color{m_name + " + " + other.m_name, (m_r + other.m_r) / 2, (m_g + other.m_g) / 2, (m_b + other.m_b) / 2};}
Utilisation dans **main.cpp** :
Color a {"Rouge", 255, 0, 0};Color b {"Bleu", 0, 0, 255};Color c {"Rouge2", 255, 0, 0};std::cout << "--- Test operator== ---\n";if (a == b) { std::cout << "a et b sont identiques\n"; }else { std::cout << "a et b sont différentes\n"; } // Différentesif (a == c) { std::cout << "a et c sont identiques\n"; } // Identiques (mêmes RGB)else { std::cout << "a et c sont différentes\n"; }std::cout << "\n--- Test operator+ ---\n";Color somme = a + b;somme.display(); // "Rouge + Bleu" R:127 G:0 B:127
Solution - Défi 2
Déclarations dans **colors.h** (section **public** de **Color**) :
// Modifie la composante rouge avec clamping automatique.// value est BY VALUE - petit int, copie triviale.void setR(int value);// Modifie la composante verte avec clamping automatique.void setG(int value);// Modifie la composante bleue avec clamping automatique.void setB(int value);
Définitions dans **colors.cpp** :
// ------------------------------------------------------------// Setters - chaque setter utilise clamp() pour garantir [0, 255]// C'est l'avantage de l'encapsulation : la validation est centralisée// ------------------------------------------------------------void Color::setR(int value) { m_r = clamp(value); }void Color::setG(int value) { m_g = clamp(value); }void Color::setB(int value) { m_b = clamp(value); }
Utilisation dans **main.cpp** :
std::cout << "--- Test setters avec clamping ---\n";Color c {"Test", 100, 100, 100};c.display(); // R:100 G:100 B:100c.setR(999); // clampé à 255c.setG(-50); // clampé à 0c.setB(128);c.display(); // R:255 G:0 B:128std::cout << "Sans encapsulation, on aurait pu écrire c.m_r = -500 !\n";std::cout << "Avec les setters, les valeurs sont toujours valides.\n";
Solution - Défi 3
Déclaration dans **colors.h** (section **public** de **Color**) :
// Retourne la couleur complémentaire (255 - chaque composante).// const - ne modifie pas l'objet courant.// Retourne un nouveau Color BY VALUE.Color invert() const;
Définition dans **colors.cpp** :
// ------------------------------------------------------------// invert - retourne la couleur complémentaire// Calcul : (255 - r, 255 - g, 255 - b)// Le nom est préfixé par "Inv:" pour identifier l'inverse// ------------------------------------------------------------Color Color::invert() const { return Color{"Inv:" + m_name, 255 - m_r, 255 - m_g, 255 - m_b};}
Point clé : La méthode invert() est const - elle ne modifie pas l’objet courant, elle retourne un nouvel objet. C’est un Design Pattern courant : les opérations qui transforment un objet retournent une copie modifiée plutôt que de modifier l’original.