У світі програмування часто можна зустріти розробників, які використовують різноманітні інструменти та патерни проектування, не розуміючи їх глибинної суті. Це призводить до неправильного застосування цих інструментів та, як наслідок, до створення коду, який важко підтримувати та розвивати.
Розуміння фундаментальних принципів та причин їх виникнення дозволяє приймати зважені рішення щодо архітектури програми. Коли розробник глибоко розуміє, чому певний принцип важливий, він може краще оцінити, коли його варто застосовувати, а коли можна обійтися простішим рішенням. Це особливо важливо у контексті принципів SOLID, які часто неправильно трактуються як догми, а не як інструменти.
Важливо пам'ятати, що будь-який інструмент - це засіб вирішення конкретної проблеми. Сліпе слідування принципам без розуміння їх призначення може призвести до надмірного ускладнення коду та появи абстракцій, які не приносять реальної користі. Тому перед тим, як застосовувати будь-який принцип чи патерн, варто чітко розуміти, яку проблему він вирішує та які переваги надає у конкретному випадку.
SOLID - це акронім п'яти принципів об'єктно-орієнтованого проектування, які допомагають створювати більш підтримуваний, гнучкий та масштабований код.
Розглянемо кожен принцип окремо з прикладами реалізації на Golang.
Принцип єдиної відповідальності стверджує, що кожен клас повинен мати лише одну причину для змін. У контексті 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, ми зможемо це зробити, не зачіпаючи інші частини системи.
Принцип відкритості/закритості (OCP) є одним з фундаментальних принципів об'єктно-орієнтованого проектування. Він стверджує, що програмні сутності (класи, модулі, функції тощо) повинні бути відкриті для розширення, але закриті для модифікації. Це означає, що:
Цей принцип допомагає зберегти стабільність системи при її розширенні та мінімізує ризик появи нових помилок у вже працюючому коді.