Sobes.tech
Middle+
164

Πώς κατανοείτε τις αρχές SOLID βάσει της εμπειρίας σας;

Απάντηση από AI

sobes.tech AI

SOLID — ένα σύνολο πέντε αρχών αντικειμενοστραφούς σχεδίασης (ООД), που βοηθούν στη δημιουργία ευέλικτων, επεκτάσιμων και εύκολα συντηρημένων συστημάτων. Βασιζόμενος στην εμπειρία μου, καταλαβαίνω κάθε αρχή ως εξής:

  1. S - Single Responsibility Principle (Αρχή της Μοναδικής Ευθύνης): Κάθε κλάση πρέπει να έχει μόνο έναν λόγο για αλλαγή. Αυτό σημαίνει ότι η κλάση πρέπει να ευθύνεται μόνο για μια συγκεκριμένη λειτουργία ή μέρος του συστήματος. Αυτό διευκολύνει την κατανόηση, δοκιμή και τροποποίηση της κλάσης, ελαχιστοποιώντας τις παρενέργειες κατά τις αλλαγές.

    class ReportGenerator {
        // Κύρια ευθύνη: δημιουργία αναφοράς
        public void generateReport(Data data) {
            // Λογική δημιουργίας αναφοράς
        }
    
        // Παραβίαση του SRP: αν αυτή η κλάση ασχολείται επίσης με την αποθήκευση της αναφοράς
        // public void saveReport(Report report) { /* ... */ }
    }
    
  2. O - Open/Closed Principle (Αρχή του Ανοιχτού/Κλειστού): Οντότητες λογισμικού (κλάσεις, μονάδες, λειτουργίες κ.λπ.) πρέπει να είναι ανοιχτές για επέκταση, αλλά κλειστές για τροποποίηση. Αυτό επιτυγχάνεται μέσω χρήσης αφαιρέσεων (διασυνδέσεις, αφηρημένες κλάσεις) και πολυμορφισμού. Η νέα λειτουργικότητα προστίθεται με τη δημιουργία νέων υλοποιήσεων των υφιστάμενων αφαιρέσεων, χωρίς να αλλάζει ο υπάρχων κώδικας.

    interface Shape {
        double calculateArea();
    }
    
    class Circle implements Shape {
        private double radius;
        public Circle(double radius) { this.radius = radius; }
        @Override
        public double calculateArea() { return Math.PI * radius * radius; }
    }
    
    class Square implements Shape {
        private double side;
        public Square(double side) { this.side = side; }
        @Override
        public double calculateArea() { return side * side; }
    }
    
    class AreaCalculator {
        // Ανοιχτό για επέκταση: μπορούμε να προσθέσουμε νέες μορφές, χωρίς να αλλάξουμε αυτή τη μέθοδο
        public double calculateTotalArea(Shape[] shapes) {
            double totalArea = 0;
            for (Shape shape : shapes) {
                totalArea += shape.calculateArea();
            }
            return totalArea;
        }
    }
    
  3. L - Liskov Substitution Principle (Αρχή Υποκατάστασης του Liskov): Οι υποτύποι πρέπει να είναι πλήρως εναλλάξιμοι με τους βασικούς τύπους τους. Αυτό σημαίνει ότι ο κώδικας πελάτη που εργάζεται με τον βασικό τύπο πρέπει να λειτουργεί σωστά και με οποιονδήποτε υποτύπο, χωρίς να χρειάζεται να γνωρίζει την συγκεκριμένη υλοποίηση του υποτύπου. Στην πράξη, αυτό συχνά σημαίνει ότι οι κλάσεις που κληρονομούν δεν πρέπει να παραβιάζουν τα συμβόλαια που ορίζονται από την βασική κλάση ή διασύνδεση.

    class Rectangle {
        protected int width;
        protected int height;
    
        public void setWidth(int width) { this.width = width; }
        public void setHeight(int height) { this.height = height; }
    
        public int getArea() { return width * height; }
    }
    
    class SquareLSP extends Rectangle {
        @Override
        public void setWidth(int width) {
            this.width = width;
            this.height = width; // Παραβίαση LSP, αν ο πελάτης αναμένει ανεξαρτησία
        }
    
        @Override
        public void setHeight(int height) {
            this.width = height;
            this.height = height; // Παραβίαση LSP
        }
    }
    
  4. I - Interface Segregation Principle (Αρχή του Διαχωρισμού των Διασυνδέσεων): Οι πελάτες δεν πρέπει να εξαρτώνται από διασυνδέσεις που δεν χρησιμοποιούν. Είναι καλύτερο να υπάρχουν πολλές μικρές, εξειδικευμένες διασυνδέσεις παρά μία μεγάλη, "παχύσαρκη". Αυτό μειώνει τη συσχέτιση και επιτρέπει στις κλάσεις να υλοποιούν μόνο τις λειτουργίες που πραγματικά χρειάζονται.

    interface Worker { // "Παχύ" διασύνδεση
        void work();
        void eat();
        void sleep();
    }
    
    interface Workable { // Διαχωρισμένες διασυνδέσεις
        void work();
    }
    
    interface Feedable {
        void eat();
    }
    
    interface Sleepable {
        void sleep();
    }
    
    class HumanWorker implements Workable, Feedable, Sleepable {
        // Υλοποιεί όλες τις απαραίτητες διασυνδέσεις
        // ...
    }
    
    class RobotWorker implements Workable {
        // Υλοποιεί μόνο το Workable, δεν εξαρτάται από Feedable και Sleepable
        // ...
    }
    
  5. D - Dependency Inversion Principle (Αρχή Αντιστροφής Εξαρτήσεων):

    • Τα modules υψηλού επιπέδου δεν πρέπει να εξαρτώνται από τα modules χαμηλού επιπέδου. Και τα δύο πρέπει να εξαρτώνται από αφαιρέσεις.
    • Οι αφαιρέσεις δεν πρέπει να εξαρτώνται από λεπτομέρειες. Οι λεπτομέρειες πρέπει να εξαρτώνται από τις αφαιρέσεις. Αυτή η αρχή υλοποιείται συχνά μέσω injection εξαρτήσεων, όπου οι εξαρτήσεις μιας κλάσης παρέχονται από το εξωτερικό και όχι δημιουργούνται από αυτήν. Αυτό μειώνει το coupling και διευκολύνει το testing.
    // Παραβίαση DIP: Το HighLevelModule εξαρτάται άμεσα από το LowLevelModule
    class LowLevelModule {
        public void doLowLevelStuff() { /* ... */ }
    }
    
    class HighLevelModule {
        private LowLevelModule lowLevel = new LowLevelModule(); // Άμεση εξάρτηση
    
        public void doHighLevelStuff() {
            lowLevel.doLowLevelStuff();
        }
    }
    
    // Εφαρμογή DIP: και οι δύο εξαρτώνται από την αφαιρεση
    interface Service {
        void execute();
    }
    
    class ConcreteService implements Service {
        @Override
        public void execute() { /* Λεπτομέρειες υλοποίησης */ }
    }
    
    class ClientModule {
        private final Service service; // Εξάρτηση από την αφαιρεση
    
        // Η εξάρτηση εισάγεται (π.χ. μέσω constructor)
        public ClientModule(Service service) {
            this.service = service;
        }
    
        public void doSomething() {
            service.execute(); // Λειτουργεί με την αφαιρεση
        }
    }
    

Η τήρηση των αρχών SOLID, κατά την εμπειρία μου, οδηγεί στη δημιουργία πιο καθαρού, εύκολα επεκτάσιμου και συντηρήσιμου κώδικα, που είναι κρίσιμο για την μακροπρόθεσμη ανάπτυξη των προγραμμάτων.