Принципи SOLID: навіщо вони потрібні та як застосовувати на практиці
Розуміння принципів SOLID допомагає писати код, який легко підтримувати, масштабувати та тестувати. Розберемо, навіщо потрібні ці принципи та як їх використовувати на практиці.
Що таке принципи SOLID?
Принципи SOLID — це п’ять основних правил об'єктно-орієнтованого програмування, сформульованих Робертом Мартіном ("Дядечком Бобом"). Ці принципи спрямовані на створення структурованого, зрозумілого та гнучкого коду. Якщо їх дотримуватися, ваш проєкт стане легше адаптувати до змін, а також знизиться ризик помилок при доопрацюваннях.
Розшифрування абревіатури SOLID:
- S — Single Responsibility Principle (Принцип єдиної відповідальності)
- O — Open/Closed Principle (Принцип відкритості/закритості)
- L — Liskov Substitution Principle (Принцип підстановки Барбари Лісков)
- I — Interface Segregation Principle (Принцип розділення інтерфейсу)
- D — Dependency Inversion Principle (Принцип інверсії залежностей)
Курс з вивчення Java
Можете пройти наш безкоштовний курс з вивчення Java
Single Responsibility Principle (SRP)
Принцип єдиної відповідальності стверджує, що кожен клас повинен мати лише одну причину для зміни, тобто виконувати тільки одну задачу. Якщо клас виконує декілька функцій, при зміні однієї з них можуть непередбачувано зламатися інші.
Приклад порушення SRP:
class Report {
public String generateReport() {
// Логіка генерації звіту
}
public void saveToFile(String filename) {
// Логіка збереження звіту у файл
}
}Тут клас і генерує звіт, і зберігає його на диск. Краще винести збереження в окремий клас.
Як виправити:
class Report {
public String generateReport() {
// Логіка генерації звіту
}
}
class ReportSaver {
public void saveToFile(Report report, String filename) {
// Логіка збереження звіту
}
}Open/Closed Principle (OCP)
Принцип відкритості/закритості означає, що програмні сутності мають бути відкриті для розширення, але закриті для модифікації. Іншими словами, додавати нову поведінку можна без зміни вже існуючого коду.
Поганий приклад:
class AreaCalculator {
public double calculateArea(Object shape) {
if (shape instanceof Circle) {
// Обчислення площі круга
} else if (shape instanceof Square) {
// Обчислення площі квадрата
}
return 0;
}
}При додаванні нової фігури доведеться змінювати код калькулятора. Це порушує принцип OCP.
Хороший приклад:
interface Shape {
double calculateArea();
}
class Circle implements Shape {
public double calculateArea() {
// Площа круга
}
}
class Square implements Shape {
public double calculateArea() {
// Площа квадрата
}
}
class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.calculateArea();
}
}Liskov Substitution Principle (LSP)
Принцип підстановки Лісков стверджує: якщо клас S є підкласом класу T, то об'єкти типу T можуть бути замінені об'єктами типу S без зміни правильності програми.
Приклад порушення LSP:
class Bird {
public void fly() {
// Птах летить
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Пінгвіни не вміють літати");
}
}Пінгвін не вміє літати, отже, наслідування від Bird є некоректним. Потрібно переглянути ієрархію.
Як виправити:
interface Bird {
void eat();
}
interface FlyingBird extends Bird {
void fly();
}
class Sparrow implements FlyingBird {
public void fly() {
// Горобець летить
}
public void eat() {
// Горобець їсть
}
}
class Penguin implements Bird {
public void eat() {
// Пінгвін їсть
}
}Курс з вивчення Java
Можете пройти наш безкоштовний курс з вивчення Java
Interface Segregation Principle (ISP)
Принцип розділення інтерфейсу стверджує, що клієнти не повинні залежати від інтерфейсів, які вони не використовують. Краще створювати кілька спеціалізованих інтерфейсів, ніж один загальний і громіздкий.
Поганий приклад:
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() {
// Робот працює
}
public void eat() {
// Робот не їсть
}
}Роботи не їдять, тому їх змушують реалізувати непотрібний метод.
Хороший приклад:
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Human implements Workable, Eatable {
public void work() {
// Людина працює
}
public void eat() {
// Людина їсть
}
}
class Robot implements Workable {
public void work() {
// Робот працює
}
}Dependency Inversion Principle (DIP)
Принцип інверсії залежностей рекомендує будувати залежності через абстракції, а не через конкретні реалізації. Модулі верхнього рівня не повинні залежати від модулів нижчого рівня безпосередньо.
Поганий приклад:
class LightBulb {
public void turnOn() {}
public void turnOff() {}
}
class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
// Керування лампочкою
}
}Перемикач жорстко пов'язаний із конкретною реалізацією лампочки.
Хороший приклад:
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {}
public void turnOff() {}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
// Керування пристроєм
}
}Висновок
Принципи SOLID — це основа для створення чистого, підтримуваного та масштабованого коду. Вони допомагають уникати типових помилок проєктування, спрощують командну роботу та прискорюють розробку. Дотримання цих правил вимагає досвіду та уважності, але з часом принципи стають невід'ємною частиною мислення професійного розробника.
Більше цікавих новин
Топ-10 сайтов, построенных с помощью Node.Js
Чому всі говорять про Deno та Bun: майбутнє без Node.JS?
Какой Linux дистрибутив выбрать? ТОП 7
Изменение статуса при помощи Python и API Вконтакте