Logo ConstructiCat
CodeBust.
Parcourir la section ▾

God object.

Un God object est un antipatron : une seule classe qui sait ou fait trop de choses et concentre l’essentiel des données et du comportement du programme.

Un God object est, dans le domaine de la programmation orientée objet, un objet qui reconnaît trop de choses ou fait trop de choses. Le god object est un exemple d'antipattern (ou anti-patron).

Description

Le principe général de la programmation structurée est de s'attaquer à un problème important en le divisant en plus petits problèmes à résoudre (stratégie de diviser pour régner). Une fois chacun des petits problèmes résolus, le problème général est automatiquement réglé. Ainsi, il n'y a qu'un objet auquel il doit être connu ou renseigné : lui-même. De la même façon, il n'y a qu'un seul ensemble de problèmes auquel un objet doit se confronter : l'ensemble des siens propres.

La programmation « god object » ne suit pas cette approche. Au lieu de cela, la plus grande partie du programme consiste en un seul bloc qui est renseigné sur tout et maintient constamment à jour données ou informations sur le programme, et fournit la plupart des fonctions et des algorithmes qui utilisent ces données. Du fait que cet objet supporte et organise tellement d'informations à lui seul, il joue un rôle identique à celui d'un dieu. Au lieu de blocs de programme communicant indépendamment entre eux et sans intermédiaire, les autres parties du programme sont dépendants du god object pour communiquer et prendre leurs informations. Comme le god object est référencé par tout le reste de la programmation, la maintenance de celle-ci devient très difficile, y compris dans les plus ordonnés des programmes.

Un god object est la version « orientée-objet » de l'incapacité à concevoir correctement les sous-programmes dans un langage de programmation procédural, ou d'utiliser trop de variables globales pour y stocker des informations sur l'état du programme à un moment donné (comme les drapeaux).

Bien que la création d'un god object soit considérée comme une mauvaise pratique de programmation, cette technique est à l'occasion utilisée dans les environnements critiques de programmation (comme les microcontrôleurs), où le gain dans la vitesse d'exécution et la centralisation du contrôle sont des facteurs plus importants que la facilité de maintenance et l'élégance de la programmation.

Notes et références

Voir aussi

Bibliographie

  • (en) Arthur J. Riel, Object-Oriented Design Heuristics, Boston, MA, Addison-Wesley, 1996, 8e éd., 379 p., relié (ISBN 978-0-201-63385-6, LCCN 95048396), « Chapter 3: Topologies of Action-Oriented Vs. Object-Oriented Applications »

« 3.2: Do not create god classes/objects in your system. Be very suspicious of an abstraction whose name contains Driver, Manager, System, or Subsystem. »

Liens externes

Exemple

La classe GameManager de cet exemple en C++ est un God object typique : elle gère les joueurs, la logique du jeu, l’affichage, la lecture des entrées et la sauvegarde sur disque.

import std;

using std::string;
using std::vector;

class GameManager {
private:
    vector<string> players;
    int score = 0;
    bool running = false;
public:
    GameManager() = default;
    ~GameManager() = default;

    void addPlayer(const string& name) {
        players.push_back(name);
        std::println("Added player: {}", name);
    }

    void listPlayers() const {
        std::println("Players:");
        for (const string& p : players) {
            std::println(" - {}", p);
        }
    }

    void startGame() {
        running = true;
        score = 0;
        std::println("Game started!");
    }

    void updateGame() {
        if (running) {
            score += 10;
            std::println("Score updated: {}", score);
        }
    }

    void endGame() {
        running = false;
        std::println("Game over! Final score: {}", score);
    }

    void draw() {
        std::println("[Rendering Game Screen]");
    }

    void handleInput(const string& input) {
        if (input == "quit") {
            endGame();
        } else if (input == "score") {
            std::println("Current score: {}", score);
        } else {
            std::println("Unknown input");
        }
    }

    void saveGame() {
        std::println("Saving game state to disk...");
    }

    void loadGame() {
        std::println("Loading game state from disk...");
    }

    void run() {
        // ...
    }
};

Il est préférable de répartir ces responsabilités entre des classes distinctes :

import std;

using std::string;
using std::vector;

class PlayerManager {
private:
    vector<string> players;
public:
    PlayerManager() = default;
    ~PlayerManager() = default;

    void addPlayer(const string& name) {
        players.push_back(name);
        std::println("Added player: {}", name);
    }

    void listPlayers() const {
        std::println("Players:");
        for (const string& p : players) {
            std::println(" - {}", p);
        }
    }
};

class GameLogic {
private:
    int score = 0;
    bool running = false;
public:
    GameLogic() = default;
    ~GameLogic() = default;

    void startGame() {
        running = true;
        score = 0;
        std::println("Game started!");
    }

    void updateGame() {
        if (running) {
            score += 10;
            std::println("Score updated: {}", score);
        }
    }

    int getScore() const noexcept {
        return score;
    }

    void endGame() {
        running = false;
        std::println("Game over! Final score: {}", score);
    }
};

class Renderer {
public:
    Renderer() = default;
    ~Renderer() = default;

    void draw() {
        std::println("[Rendering Game Screen]");
    }
};

class InputHandler {
private:
    GameLogic& logic;
public:
    explicit InputHandler(GameLogic& logic):
        logic{logic} {}

    ~InputHandler() = default;

    void handleInput(const string& input) {
        if (input == "quit") {
            logic.endGame();
        } else if (input == "score") {
            std::println("Current score: {}", logic.getScore());
        } else {
            std::println("Unknown input");
        }
    }
};

class SaveSystem { 
public:
    SaveSystem() = default;
    ~SaveSystem() = default;

    void saveGame() {
        std::println("Saving game state to disk...");
    }

    void loadGame() {
        std::println("Loading game state from disk...");
    }
};

class GameManager {
private:
    PlayerManager players;
    GameLogic logic;
    Renderer renderer;
    InputHandler input{logic};
    SaveSystem saves;
public:
    GameManager() = default;
    ~GameManager() = default;

    void run() {
        // ...
    }
};

Chaque classe n’a plus qu’une seule raison de changer, et GameManager se contente de les relier. C’est le remède habituel à une classe trop volumineuse, appliqué au moyen du refactoring Extraire une classe.

Adapté de l’article Wikipédia «God object» (révision 179159010), disponible sous licence CC BY-SA 4.0 ; le texte a été modifié et des exemples ont été ajoutés.