SOLID — це абревіатура, що позначає Принцип єдиної відповідальності (SRP), Принцип відкритості/закритості (OCP), Принцип заміщення Ліскова (LSP), Принцип розділення інтерфейсів (ISP) та Принцип інверсії залежностей (DIP).
Ці принципи допомагають розробникам створювати більш зрозумілі, стійкі, керовані та гнучкі програмні системи.
1. Принцип єдиної відповідальності (SRP)
Клас повинен мати лише одну відповідальність. Кожен клас має виконувати конкретне завдання, щоб правильно виконувати свою роботу. Так можна легко знайти помилки та проблеми в коді.
До застосування SRP:
public class EmployeeService
{
public EmployeeResultDto AddEmployee(EmployeeDto employee) { }
public AuthResultDto GenerateToken(EmployeeDto employee) { }
}
Після застосування SRP:
public class EmployeeService
{
public EmployeeResultDto AddEmployee(EmployeeDto employee) { }
}
public class AuthService
{
public AuthResultDto GenerateToken(EmployeeDto employee) { }
}
2. Принцип відкритості/закритості (OCP)
Клас повинен бути відкритим для розширення, але закритим для модифікації. Це допомагає зробити код розширюваним, коли додаються нові функції чи поведінка, замість того, щоб змінювати старий код.
До застосування OCP:
public class NotificationService
{
public void SendMessage(string message)
{
if (MessageType == "SMS")
Console.WriteLine($"Sending {message} with SMS.");
else if (MessageType == "Email")
Console.WriteLine($"Sending {message} with Email.");
}
}
Після застосування OCP:
public interface INotificationService
{
void Send(string message) { }
}
public class SmsService : INotificationService
{
public void Send(string message)
{
// Code logic to send Sms operation
}
}
public class MailService : INotificationService
{
public void Send(string message)
{
// Code logic to send Mail operation
}
}
public class PushNotificationService : INotificationService
{
public void Send(string message)
{
// Code logic to send Push Notification operation
}
}
3. Принцип заміщення Ліскова (LSP)
Об’єкт похідного класу має бути здатним замінити об’єкт базового класу без виникнення помилок у системі або зміни поведінки базового класу. Базовий клас може замінити похідний, не впливаючи на систему.
До застосування LSP:
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
public virtual void TakeRaise()
{
Console.WriteLine($"{Name} has received a raise!");
Salary += 500; // Raise increases salary
}
}
public class ShareHolder : Employee
{
public decimal SharesOwned { get; set; }
public override void TakeRaise()
{
Console.WriteLine($"{Name} is a shareholder, no raise needed.");
}
}
Після застосування LSP:
public abstract class Employee
{
public string Name { get; set; }
public abstract void GetAnnualBonus();
}
public class CompanyWorker : Employee
{
public override void GetAnnualBonus()
{
TakeRaise();
}
public virtual void TakeRaise()
{
Console.WriteLine($"This employee has received a raise!");
}
}
public class CompanyPartner : Employee
{
public override void GetAnnualBonus()
{
ReceiveDividend();
}
public virtual void ReceiveDividend()
{
Console.WriteLine($"This employee received a dividend based on shares.");
}
}
public class Employee : CompanyWorker
{
public override void TakeRaise()
{
Console.WriteLine($"{Name} has received a raise!");
}
}
public class ShareHolder : CompanyPartner
{
public override void ReceiveDividend()
{
Console.WriteLine($"{Name} received a dividend based on shares.");
}
}
## 4. Принцип розділення інтерфейсів (ISP)
Жоден код не повинен залежати від інтерфейсів, які він не використовує. Важливо, щоб інтерфейси були розбиті на менші, спеціалізовані частини.
До застосування ISP:
public interface IEmployeeService
{
void Hire(EmployeeDto employee);
void Fire(EmployeeDto employee);
void ProcessSalary(EmployeeDto employee);
}
public class PayrollSystem : IEmployeeService
{
public void Hire(EmployeeDto employee) { /* Not relevant for PayrollSystem / }
public void Fire(EmployeeDto employee) { / Not relevant for PayrollSystem / }
public void ProcessSalary(EmployeeDto employee) { / Payroll-specific logic */ }
}
```
Після застосування ISP:
public interface IPayrollService
{
void ProcessSalary(EmployeeDto employee);
}
public interface IEmployeeManagementService
{
void Hire(EmployeeDto employee);
void Fire(EmployeeDto employee);
}
public class PayrollSystem : IPayrollService
{
public void ProcessSalary(EmployeeDto employee)
{
Console.WriteLine($"Salary processed for {employee.Name}");
}
}
public class EmployeeManager : IEmployeeManagementService
{
public void Hire(EmployeeDto employee)
{
Console.WriteLine($"{employee.Name} hired.");
}
public void Fire(EmployeeDto employee)
{
Console.WriteLine($"{employee.Name} fired.");
}
}
5. Принцип інверсії залежностей (DIP)
Модулі високого рівня не повинні залежати від модулів низького рівня. Це повинно бути спрямоване на зменшення зв’язності між модулями системи. Якщо створюється сильно зв’язаний додаток, для заміни цієї залежності потрібно буде виконати багато роботи, змінюючи ці залежності в кожному методі, де вони використовуються. Також значно важче проводити юніт-тестування цього рівня, оскільки важче змоделювати цю залежність.
До застосування DIP:
public class EmployeeService
{
private readonly EmployeeRepository _employeeRepository;
public EmployeeService()
{
_employeeRepository = new EmployeeRepository(); // Direct dependency
}
public void AddEmployee(EmployeeDto employee)
{
_employeeRepository.Add(employee);
}
}
Після застосування DIP:
public interface IEmployeeRepository
{
void Add(EmployeeDto employee);
}
public class EmployeeRepository : IEmployeeRepository
{
public void Add(EmployeeDto employee)
{
Console.WriteLine($"Employee {employee.Name} added to the repository.");
}
}
public class EmployeeService
{
private readonly IEmployeeRepository _employeeRepository;
// Dependency injected via constructor
public EmployeeService(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
public void AddEmployee(Employee employee)
{
_employeeRepository.Add(employee);
}
}
Висновок
Розуміння та застосування принципів SOLID у проектах .NET Core дозволяє створювати програмні дизайни, які є більш підтримуваними, гнучкими та масштабованими. Ці принципи допомагають писати чистий, модульний і тестований код, що в кінцевому підсумку призводить до кращої якості програмного забезпечення.
Перекладено з: Understanding SOLID Principles in .Net Core(C#) with Employee Example