← Назад к вопросам

Что такое связность кода?

2.2 Middle🔥 171 комментариев
#ООП и проектирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое связность кода?

Связность (cohesion) — это мера того, насколько элементы внутри модуля/класса связаны между собой и работают вместе для достижения единой цели. Это фундаментальный принцип качественного проектирования.

Высокая связность = хороший код. Низкая связность = плохой, трудно поддерживаемый код.

Высокая связность (HIGH COHESION) — ХОРОШО

// Класс BankAccount имеет высокую связность
// Все методы работают с одной ответственностью: управление банковским счётом

class BankAccount {
private:
    double balance;
    std::string accountNumber;
    std::vector<Transaction> transactions;
    
public:
    // Все методы логически связаны — работают с одним счётом
    void deposit(double amount) {
        balance += amount;
        recordTransaction("DEPOSIT", amount);
    }
    
    void withdraw(double amount) {
        if(amount <= balance) {
            balance -= amount;
            recordTransaction("WITHDRAW", amount);
        }
    }
    
    double getBalance() const { return balance; }
    
    const std::vector<Transaction>& getTransactions() const { 
        return transactions; 
    }
    
private:
    void recordTransaction(const std::string& type, double amount) {
        transactions.push_back(Transaction{type, amount, time(nullptr)});
    }
};

Почему это хорошо:

  • Каждый метод имеет отношение к управлению счётом
  • Методы используют одни и те же данные (balance, transactions)
  • Легко понять цель класса
  • Легко тестировать
  • Легко изменять

Низкая связность (LOW COHESION) — ПЛОХО

// Класс Utility имеет НИЗКУЮ связность
// Методы не имеют общей цели

class Utility {
public:
    // Что это вообще? Почему эти методы вместе?
    
    // Работа с датами
    std::string formatDate(const std::tm& t) {
        return std::to_string(t.tm_year) + "-" + std::to_string(t.tm_mon) + "-" + std::to_string(t.tm_mday);
    }
    
    // Работа с сетевыми сокетами
    bool connectSocket(const std::string& host, int port) {
        // Реализация
        return true;
    }
    
    // Работа с шифрованием
    std::string encryptAES(const std::string& plaintext, const std::string& key) {
        // Реализация
        return "encrypted";
    }
    
    // Работа с базой данных
    void executeSQL(const std::string& query) {
        // Реализация
    }
    
    // Работа с файловой системой
    void writeFile(const std::string& path, const std::string& content) {
        // Реализация
    }
};

// Использование (ужас!)
Utility util;
util.formatDate(now);  // "Дай мне дату"
util.connectSocket("localhost", 8080);  // "Подключись к сокету"
util.encryptAES("secret", "key");  // "Зашифруй"
util.executeSQL("SELECT * FROM users");  // "Запроси БД"
util.writeFile("/tmp/data.txt", "data");  // "Напиши файл"

Почему это плохо:

  • Класс делает ВСЁ сразу (нарушение SRP)
  • Невозможно переиспользовать (если нужна шифровка, тащишь весь Utility)
  • Трудно тестировать (зависит от всего)
  • Трудно изменять (одна ошибка может сломать что угодно)
  • Невозможно понять цель класса

Типы связности (от худшей к лучшей)

1. COINCIDENTAL (Совпадающая) — Худшая

// Методы вообще не связаны между собой
class Random {
public:
    void printHello() { std::cout << "Hello"; }
    int calculateTax(double income) { return income * 0.13; }
    bool isPrime(int n) { /* проверка */ return true; }
};

2. LOGICAL (Логическая)

// Методы связаны только логически ("работа с I/O")
class IOHandler {
public:
    void readFile(const std::string& path) { /* чтение файла */ }
    void readSocket(int fd) { /* чтение сокета */ }
    void readDatabase() { /* чтение БД */ }
};
// Проблема: если нужно читать с диска, не нужны остальные методы

3. TEMPORAL (Временная)

// Методы выполняются в одно время, но логически не связаны
class Initialization {
public:
    void initDatabase() { /* настройка БД */ }
    void loadConfig() { /* загрузка конфига */ }
    void warmupCache() { /* прогрев кэша */ }
};
// Хорошо для инициализации, но методы не совпадают логически

4. PROCEDURAL (Процедурная)

// Методы работают вместе для одного процесса
class UserRegistration {
public:
    void validateInput(const UserData& data) { /* ... */ }
    void checkDuplicates(const std::string& email) { /* ... */ }
    void saveToDatabase(const UserData& data) { /* ... */ }
    void sendConfirmationEmail(const std::string& email) { /* ... */ }
};
// Они работают по порядку (process), но частично связаны

5. COMMUNICATIONAL (Коммуникационная)

// Методы работают с одними и теми же данными
class Product {
private:
    std::string name;
    double price;
    int stock;
    std::string category;
    
public:
    void setPrice(double p) { price = p; }
    void setStock(int s) { stock = s; }
    double calculateTax() { return price * 0.18; }
    bool isInStock() { return stock > 0; }
    void printDetails() { /* использует name, price, category */ }
};
// Все методы работают с одними данными, но не обязательно связаны

6. SEQUENTIAL (Последовательная)

// Выход одного метода используется как вход для другого
class DataProcessor {
private:
    std::vector<int> data;
    
public:
    void loadData(const std::string& file) {
        // Загружает в data
    }
    
    void filterData() {
        // Использует данные из loadData, изменяет data
        data.erase(std::remove_if(data.begin(), data.end(), 
                                  [](int x) { return x < 0; }), 
                   data.end());
    }
    
    std::vector<int> sortData() {
        // Использует результат filterData
        std::sort(data.begin(), data.end());
        return data;
    }
};
// Хорошая связность, методы зависят друг от друга

7. FUNCTIONAL (Функциональная) — Лучшая

// Все методы работают вместе для одной цели
class BankAccount {
private:
    double balance;
    std::string accountNumber;
    std::vector<Transaction> transactionHistory;
    
public:
    // Все методы для управления одним счётом
    void deposit(double amount) {
        balance += amount;
        recordTransaction("DEPOSIT", amount);
    }
    
    void withdraw(double amount) {
        if(amount <= balance) {
            balance -= amount;
            recordTransaction("WITHDRAW", amount);
        }
    }
    
    double getBalance() const { return balance; }
    
    const std::vector<Transaction>& getHistory() const {
        return transactionHistory;
    }
    
private:
    void recordTransaction(const std::string& type, double amount) {
        transactionHistory.push_back({type, amount, time(nullptr)});
    }
};

Связность функций/методов

Низкая связность:

// Функция делает несвязанные вещи
void processUserRequest(int userId) {
    // Загружаем пользователя
    User user = database.getUser(userId);
    
    // Отправляем email
    emailSender.send(user.email, "Welcome");
    
    // Логируем в audit
    auditLog.write("User logged in: " + user.name);
    
    // Обновляем кэш
    cache.set("user_" + std::to_string(userId), user);
    
    // Если есть рефераль, добавляем бонус
    if(user.referral_code) {
        Referral ref = database.getReferral(user.referral_code);
        ref.bonus += 100;
        database.updateReferral(ref);
    }
}

Высокая связность (разделено по ответственности):

void processUserRequest(int userId) {
    User user = userService.getUserAndLoad(userId);
    userNotificationService.notifyNewUser(user);
    userAnalyticsService.logUserLogin(userId);
    referralService.creditBonusIfApplicable(user.referral_code);
}

// Или ещё лучше:
userService.processNewUser(userId);  // userService сам знает что делать

Как измерить связность?

Признаки ВЫСОКОЙ связности:

  • Методы используют много общих данных
  • Сложно извлечь один метод без других
  • Класс имеет одну, четкую ответственность
  • Все методы работают на достижение одной цели
  • Мало зависимостей между методами

Признаки НИЗКОЙ связности:

  • Методы независимы друг от друга
  • Легко вынуть один метод в отдельный класс
  • Класс делает слишком много
  • Трудно назвать класс одной фразой
  • Много зависимостей от внешних модулей

Связность и муфта (Coupling)

Связность (Cohesion) — ВНУТРЕННЯЯ:

// Как хорошо методы внутри класса работают вместе?
class UserProfile {
    // Все методы о пользователе — хорошая связность
};

Муфта (Coupling) — ВНЕШНЯЯ:

// Как класс зависит от других классов?
class UserProfile {
    DatabaseConnection db;  // Зависит от db
    EmailSender emailer;    // Зависит от emailer
    // Высокая муфта — плохо
};

Идеал: HIGH COHESION + LOW COUPLING

Практический пример: рефакторинг для связности

ДО (низкая связность):

class Order {
public:
    void addItem(int productId, int quantity) { /* ... */ }
    void removeItem(int productId) { /* ... */ }
    void calculateTotal() { /* ... */ }
    void validateAddress(const Address& addr) { /* ... */ }
    void callPaymentGateway(const std::string& cardToken) { /* ... */ }
    void sendConfirmationEmail(const std::string& email) { /* ... */ }
    void logAnalytics(const std::string& event) { /* ... */ }
};

ПОСЛЕ (высокая связность):

class Order {
public:
    void addItem(int productId, int quantity) { /* ... */ }
    void removeItem(int productId) { /* ... */ }
    void getTotal() const { /* ... */ }
};

class OrderProcessor {
public:
    bool process(Order& order, const Address& addr, const std::string& cardToken) {
        if(!validateAddress(addr)) return false;
        if(!processPayment(cardToken)) return false;
        notifyCustomer();
        logMetrics();
        return true;
    }
};

Итог

Связность — это:

  • Мера того, насколько методы класса работают вместе
  • Фундаментальный принцип хорошего дизайна
  • Противоположность SRP нарушениям
  • Ключ к поддерживаемому коду

Правило: Стремись к HIGH COHESION + LOW COUPLING. Класс должен делать одно, но хорошо.

Что такое связность кода? | PrepBro