У світі програмування часто можна зустріти розробників, які використовують різноманітні інструменти та патерни проектування, не розуміючи їх глибинної суті. Це призводить до неправильного застосування цих інструментів та, як наслідок, до створення коду, який важко підтримувати та розвивати.

Розуміння фундаментальних принципів та причин їх виникнення дозволяє приймати зважені рішення щодо архітектури програми. Коли розробник глибоко розуміє, чому певний принцип важливий, він може краще оцінити, коли його варто застосовувати, а коли можна обійтися простішим рішенням. Це особливо важливо у контексті принципів SOLID, які часто неправильно трактуються як догми, а не як інструменти.

Важливо пам'ятати, що будь-який інструмент - це засіб вирішення конкретної проблеми. Сліпе слідування принципам без розуміння їх призначення може призвести до надмірного ускладнення коду та появи абстракцій, які не приносять реальної користі. Тому перед тим, як застосовувати будь-який принцип чи патерн, варто чітко розуміти, яку проблему він вирішує та які переваги надає у конкретному випадку.

SOLID - це акронім п'яти принципів об'єктно-орієнтованого проектування, які допомагають створювати більш підтримуваний, гнучкий та масштабований код.

Розглянемо кожен принцип окремо з прикладами реалізації на Golang.

Single Responsibility Principle (S - SRP)

Принцип єдиної відповідальності стверджує, що кожен клас повинен мати лише одну причину для змін. У контексті Go це означає, що кожен пакет, структура або функція повинні виконувати лише одну конкретну задачу.

Розглянемо приклад поганої реалізації:

type User struct {
    Name string
    Email string
}

func (u *User) SaveToDatabase() error {
    // Логіка збереження користувача
    return nil

func (u *User) SendEmail() error {
    // Логіка відправки email
    return nil
}

Як бачимо з прикладу, структура User має два методи SaveToDatabase та SendEmail. Хоча це не відповідає принципу єдиної відповідальності, адже в даному випадку структура буде перевантажена методами, які їй не властиві, тобто - робота з базою даних та відправка Email листів.

Прикладом правильної реалізації буде наступний код:

type User struct {
    Name string
    Email string
}

type UserRepository struct {
    // Має підключення до БД
}

func (r *UserRepository) Save(user User) error {
    // Логіка збереження користувача
    return nil
}

type EmailService struct {
    // Має конфігурацію для відправки Email
}

func (s *EmailService) SendEmail(user User) error {
    // Логіка відправки email
    return nil
}

У цьому прикладі ми розділили відповідальність між різними структурами. Тепер структура User відповідає лише за збереження даних користувача, UserRepository займається операціями з базою даних, а EmailService відповідає за відправку електронних листів. Такий підхід робить код більш модульним, легшим для тестування та підтримки.

Кожен компонент має чітко визначену відповідальність, що відповідає принципу SRP. Якщо нам потрібно буде змінити логіку роботи з базою даних або систему відправки email, ми зможемо це зробити, не зачіпаючи інші частини системи.

Open/Closed Principle (O - OCP)

Принцип відкритості/закритості (OCP) є одним з фундаментальних принципів об'єктно-орієнтованого проектування. Він стверджує, що програмні сутності (класи, модулі, функції тощо) повинні бути відкриті для розширення, але закриті для модифікації. Це означає, що:

Цей принцип допомагає зберегти стабільність системи при її розширенні та мінімізує ризик появи нових помилок у вже працюючому коді.