Οι αρχές SOLID προτάθηκαν από τον Robert C. Martin (“Uncle Bob”) στα τέλη της δεκαετίας του 1990, για να βοηθήσουν τους προγραμματιστές να γράφουν ευέλικτο και επαναχρησιμοποιήσιμο αντικειμενοστραφή κώδικα.
Το ακρωνύμιο SOLID προκύπτει από τις αρχές:
S – Single Responsibility Principle (SRP)
O – Open/Closed Principle (OCP)
L – Liskov Substitution Principle (LSP)
I – Interface Segregation Principle (ISP)
D – Dependency Inversion Principle (DIP)
Στόχος των SOLID principles είναι να μειώσουν την σύζευξη (coupling), να αυξήσουν την συνοχή (cohesion) και να κάνουν τον κώδικα πιο ευέλικτο σε αλλαγές.
Αναλυτικά:
1️⃣S – Single Responsibility Principle (SRP)
Αρχή: Κάθε κλάση ή interface πρέπει να έχει μία και μόνο ευθύνη.
Πρόβλημα χωρίς SRP:
Αν μία κλάση κάνει πολλές δουλειές (π.χ. υπολογισμούς, αποθήκευση σε βάση, δημιουργία reports), κάθε αλλαγή σε μία από αυτές τις λειτουργίες μπορεί να σπάσει τις άλλες.
Παράδειγμα:
// Κάθε κλάση έχει μία ευθύνη
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
// Υπολογισμός μισθού
public interface ISalaryCalculator
{
decimal Calculate(Employee employee);
}
public class SalaryCalculator : ISalaryCalculator
{
public decimal Calculate(Employee employee) => employee.Salary * 1.1m;
}
// Αποθήκευση σε βάση
public interface IEmployeeRepository
{
void Save(Employee employee);
}
public class EmployeeRepository : IEmployeeRepository
{
public void Save(Employee employee)
{
// save to DB
}
}
Επεξήγηση:
Κάθε κλάση/interface έχει μία και μόνο ευθύνη, πράγμα που κάνει τον κώδικα πιο καθαρό και πιο εύκολο στη συντήρηση.
2️⃣ O – Open/Closed Principle (OCP)
Αρχή: Οι κλάσεις πρέπει να είναι ανοικτές για επέκταση αλλά κλειστές για τροποποίηση.
Πρόβλημα χωρίς OCP:
Αν τροποποιούμε υπάρχοντα κώδικα για να προσθέσουμε λειτουργικότητα, υπάρχει κίνδυνος να σπάσουμε υπάρχουσα λειτουργία.
Παράδειγμα:
public interface IDiscount
{
decimal Apply(decimal price);
}
public class VipDiscount : IDiscount
{
public decimal Apply(decimal price) => price * 0.8m;
}
public class RegularDiscount : IDiscount
{
public decimal Apply(decimal price) => price * 0.9m;
}
Επεξήγηση:
Μπορούμε να προσθέσουμε νέα είδη εκπτώσεων (π.χ. SeasonalDiscount) χωρίς να αλλάξουμε τον υπάρχοντα κώδικα, επεκτείνοντας τη λειτουργικότητα με ασφάλεια.
3️⃣ L – Liskov Substitution Principle (LSP)
Αρχή: Οι υποκλάσεις ή υλοποιήσεις interface πρέπει να μπορούν να αντικαταστήσουν τις βασικές χωρίς να σπάει η λειτουργικότητα.
Πρόβλημα χωρίς LSP:
Αν μία υποκλάση δεν μπορεί να εκτελέσει σωστά λειτουργία της βασικής κλάσης, προκαλεί σφάλματα runtime.
Παράδειγμα με interface:
public interface IFlyable
{
void Fly();
}
public class Sparrow : IFlyable
{
public void Fly() { /* πετάει */ }
}
// Penguin δεν υλοποιεί IFlyable
public class Penguin
{
// δεν πετάει, δεν αναγκάζουμε κώδικα που δεν έχει νόημα
}
Επεξήγηση:
Κανένα αντικείμενο δεν αναγκάζεται να υλοποιήσει μέθοδο που δεν μπορεί να κάνει. Ο κώδικας είναι πιο ασφαλής και σταθερός.
4️⃣ I – Interface Segregation Principle (ISP)
Αρχή: Τα interfaces πρέπει να είναι μικρά και συγκεκριμένα, όχι γενικά και μεγάλα.
Πρόβλημα χωρίς ISP:
Αν ένα interface έχει πολλές μεθόδους που δεν αφορούν όλες τις κλάσεις, οι υλοποιήσεις αναγκάζονται να γράψουν κώδικα που δεν χρειάζονται.
Παράδειγμα:
public interface IWorkable
{
void Work();
}
public interface IFeedable
{
void Eat();
}
public class HumanWorker : IWorkable, IFeedable
{
public void Work() { /* δουλεύει */ }
public void Eat() { /* τρώει */ }
}
public class RobotWorker : IWorkable
{
public void Work() { /* δουλεύει */ }
}
Επεξήγηση:
Κάθε κλάση υλοποιεί μόνο τις λειτουργίες που πραγματικά χρειάζεται. Αποφεύγουμε περιττό κώδικα.
5️⃣ D – Dependency Inversion Principle (DIP)
Αρχή: Οι υψηλού επιπέδου μονάδες δεν πρέπει να εξαρτώνται από χαμηλού επιπέδου. Και οι δύο πρέπει να εξαρτώνται από abstraction (interface).
Πρόβλημα χωρίς DIP:
Αν μια κλάση υψηλού επιπέδου εξαρτάται από συγκεκριμένη υλοποίηση χαμηλού επιπέδου, κάθε αλλαγή στη χαμηλού επιπέδου κλάση σπάει τον κώδικα.
Παράδειγμα:
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger
{
public void Log(string message) { /* γράφει σε αρχείο */ }
}
public class DatabaseLogger : ILogger
{
public void Log(string message) { /* γράφει σε DB */ }
}
public class EmployeeManager
{
private readonly ILogger _logger;
public EmployeeManager(ILogger logger)
{
_logger = logger;
}
public void AddEmployee(Employee e)
{
_logger.Log("Employee added");
}
}
Επεξήγηση:
Η EmployeeManager εξαρτάται από το abstraction ILogger, όχι από συγκεκριμένη υλοποίηση. Μπορούμε να αλλάξουμε logger χωρίς να αγγίσουμε τον κώδικα της EmployeeManager.
✅ Συμπέρασμα
Με τη χρήση interfaces:
Ο κώδικας γίνεται καθαρός και επεκτάσιμος.
Κάθε αρχή SOLID εφαρμόζεται με σαφήνεια.
Είναι εύκολο να προσθέτουμε νέες λειτουργίες χωρίς να σπάει ο υπάρχων κώδικας.
Top comments (0)