Логотип ConstructiCat
CodeBust.
Навигация по разделу ▾

Божественный объект.

Божественный объект — антипаттерн, при котором один класс знает и делает слишком много, сосредоточивая большую часть данных и поведения программы.

Боже́ственный объе́кт (англ. God object) — антипаттерн объектно-ориентированного программирования, описывающий объект, который хранит в себе «слишком много» или делает «слишком много».

Основная идея модульного программирования состоит в том, что большая задача делится на меньшие относительно независимые подзадачи (принцип «разделяй и властвуй»). В развитии модульного программирования — объектно-ориентированном программировании — этот принцип выражается в создании множества объектов, каждый из которых решает только свою собственную задачу.

Подход «божественного объекта» противоположен этому принципу: основная часть функциональности программы кодируется в одном объекте. Так как этот объект хранит большое количество данных и имеет много методов, его роль в программе становится «божественной» (всеобъемлющей).

Вместо того, чтобы общаться друг с другом непосредственно, другие объекты полагаются на божественный объект. Так как на божественный объект ссылается так много кода, его обслуживание (внесение изменений) становится сложным: велик риск сломать существующую функциональность.

Божественный объект — это объектно-ориентированный аналог отказа от использования подпрограмм в процедурном программировании или использования слишком большого количества глобальных переменных для хранения информации о состоянии программы.

Хотя божественные объекты считаются плохой практикой программирования, поскольку вредят поддерживаемости кода, они могут быть полезны для работы при ограниченных ресурсах (в микроконтроллерах или мобильных телефонах), где производительность важнее, чем поддерживаемость кода.

Литература

  • Riel, Arthur J. Chapter 3: Topologies of Action-Oriented Vs. Object-Oriented Applications // Object-Oriented Design Heuristics. — Boston, MA: Addison-Wesley, 1996. — ISBN 0-201-63385-X. — «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.»

Ссылки

Пример

Класс GameManager в этом примере на C++ — типичный божественный объект: он управляет игроками, ведёт игровую логику, отрисовывает экран, обрабатывает ввод и сохраняет игру на диск.

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() {
        // ...
    }
};

Эти обязанности лучше распределить между отдельными классами:

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() {
        // ...
    }
};

Теперь у каждого класса одна причина для изменения, а GameManager лишь связывает их вместе. Так обычно лечат большой класс — с помощью рефакторинга Извлечение класса.

Адаптировано по статье Википедии «Божественный объект» (версия 145057581), распространяется по лицензии CC BY-SA 4.0; текст отредактирован, добавлены примеры.