Принцип єдиного обов'язку (SRP) є, мабуть, найменше зрозумілим серед усіх принципів SOLID у розробці програмного забезпечення. Давайте розв'яжемо це непорозуміння і зрозуміємо, що він насправді означає для вашого коду.
Поширене непорозуміння
Коли розробники вперше чують про принцип єдиного обов'язку, вони часто думають, що це означає "модуль повинен виконувати лише одну задачу". Хоча це є дійсним принципом програмування, який застосовують при рефакторингу великих функцій на менші, це не те, про що йде мова в SRP.
Що насправді означає SRP?
Справжнє визначення SRP з часом стало яснішим:
- Початкова версія: "Модуль повинен мати одну і тільки одну причину для змін."
- Очищена версія: "Модуль повинен бути відповідальний за одну і тільки одну групу акторів."
Але що це означає на практиці? "Актор" — це група користувачів або зацікавлених сторін, які хочуть змінити систему певним чином. Це може бути відділ, команда чи будь-яка група користувачів з подібними потребами.
Практичний приклад: Клас Employee
Розглянемо практичний приклад, з яким багато розробників можуть стикнутися: клас Employee у системі зарплат. Розглянемо це поширене, але проблемне виконання:
class Employee {
public void calculatePay() { … } // Використовується бухгалтерією
public void reportHours() { … } // Використовується HR
public void save() { … } // Використовується DBAs
}
На перший погляд це може здаватися нормальним — зрештою, всі ці функції стосуються співробітників. Однак цей клас порушує SRP, оскільки обслуговує трьох різних акторів:
- Відділ бухгалтерії (звітує CFO): використовує
calculatePay()
- Відділ HR (звітує COO): використовує
reportHours()
- Адміністратори баз даних (звітують CTO): використовують
save()
Чому це порушення викликає проблеми
Проблема 1: Ненавмисне дублювання коду та зміни
Уявіть таку ситуацію:
class Employee {
private float regularHours() { … } // Спільне для обох методів
public void calculatePay() {
float hours = regularHours();
// Обчислити оплату, використовуючи години
}
public void reportHours() {
float hours = regularHours();
// Створити звіт, використовуючи години
}
}
Коли команда бухгалтерії захоче змінити, як обчислюються регулярні години, вони можуть змінити спільний метод regularHours()
. Однак ця зміна може непередбачувано вплинути на звіти HR, потенційно викликавши значні фінансові помилки. Це сталося, тому що код, який обслуговує різних акторів, був занадто тісно пов'язаний.
Проблема 2: Ризиковані злиття коду
Розглянемо цю ситуацію:
- DBA потрібно змінити метод save()
для зміни схеми бази даних
- Клерк HR потрібно оновити метод reportHours()
для нового формату звітів
Якщо два розробники працюють над цими змінами одночасно, їм доведеться зливати код. Це створює непотрібний ризик, оскільки ці зміни стосуються різних акторів і не повинні потребувати координації.
Як виправити порушення SRP
Ось три практичні рішення для виправлення цієї проблеми:
1. Повне розділення
Розділімо функціональність на три окремі класи:
class EmployeeData {
// Тільки дані, без методів
}
class PayCalculator {
private EmployeeData employeeData;
public void calculatePay() { … }
}
class HourReporter {
private EmployeeData employeeData;
public void reportHours() { … }
}
class EmployeeRepository {
private EmployeeData employeeData;
public void save() { … }
}
2. Використання патерну Facade
Створіть фасад для спрощення використання:
class EmployeeFacade {
private PayCalculator payCalculator;
private HourReporter hourReporter;
private EmployeeRepository repository;
public void calculatePay() {
payCalculator.calculatePay();
}
public void reportHours() {
hourReporter.reportHours();
}
public void save() {
repository.save();
}
}
## Збереження основних бізнес-правил поблизу даних
Якщо розрахунок заробітної плати є найважливішою функцією, ви можете структурувати це таким чином:
class Employee {
private EmployeeData data;
public void calculatePay() { … } // Найважливіша функція залишається
// Методи фасаду для інших функціональностей
public void reportHours() {
hourReporter.reportHours(data);
}
public void save() {
repository.save(data);
}
}
```
Звичайні проблеми, які вирішуються
Ви можете переживати, що це створює занадто багато класів з лише одним публічним методом кожен. Однак кожен клас, ймовірно, міститиме кілька приватних методів, які працюють разом для виконання однієї відповідальності. Ключове тут те, що ці приватні методи приховані від зовнішнього світу і можуть бути змінені без впливу на інших акторів.
Висновок
Принцип єдиного обов'язку не полягає в тому, щоб кожен модуль робив лише одну річ — йдеться про організацію коду навколо акторів, які захочуть змінити його. Коли ви розділяєте код залежно від того, хто хоче його змінити, ви створюєте більш підтримувані, гнучкі та надійні системи.
Пам'ятайте: якщо ви помічаєте, що пишете код, який можуть змінити різні відділи чи команди з різних причин, настав час подумати про розподіл цього коду відповідно до SRP.
Розуміючи та правильно застосовуючи SRP, ви можете створити код, який легше підтримувати, тестувати та змінювати, зменшуючи ризик неочікуваних побічних ефектів при внесенні змін. Ключове — думати про те, хто хоче, щоб код змінювався, а не лише про те, що цей код робить.
Якщо ви вважаєте, що цей контент був корисним або вам сподобалося його читати, будь ласка, підпишіться на мене для отримання нових статей про системний дизайн, масштабовану архітектуру та розробку на ASP.NET Core. Ваша підтримка важлива і допомагає мені створювати більш цінний контент!
- LinkedIn: Pavan Kumar
- GitHub: Pavan8374
- Website: Pavan Rambhukta
- Reddit: u/rpavank
Дякую за читання, і я з нетерпінням чекаю на зв'язок з вами! 🚀
Перекладено з: Understanding the Single Responsibility Principle: Beyond the Common Misconception