Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое связность кода?
Связность (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. Класс должен делать одно, но хорошо.