Sobes.tech
Middle+

Γιατί το μοτίβο Singleton θεωρείται αντι-μοτίβο;

sobes.tech AI

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

Το Singleton παραβιάζει τις αρχές SOLID και περιπλέκει τις δοκιμές:

  • Παραβίαση της αρχής της μοναδικής ευθύνης (SRP): Η κλάση είναι υπεύθυνη τόσο για τη λογική της όσο και για τη διαχείριση του κύκλου ζωής της (δημιουργία και πρόσβαση στο μοναδικό στιγμιότυπο).
  • Παραβίαση της αρχής ανοιχτό/κλειστό (OCP): Η επέκταση της λειτουργικότητας της κλάσης Singleton μπορεί να είναι δύσκολη χωρίς να αλλάξει ο κώδικας της.
  • Παραβίαση της αρχής αντικατάστασης του Λίσκοφ (LSP): Τα υποείδη της κλάσης Singleton μπορεί να μην ικανοποιούν τις συμβάσεις του βασικού τύπου λόγω των ιδιαιτεροτήτων της υλοποίησης Singleton.
  • Παραβίαση της αρχής αντιστροφής των εξαρτήσεων (DIP): Τα modules εξαρτώνται από μια συγκεκριμένη υλοποίηση του Singleton, όχι από αφαιρέσεις. Αυτό καθιστά δύσκολη την αντικατάσταση του αντικειμένου Singleton με ένα mock ή stub για δοκιμές.
  • Προβλήματα με τις δοκιμές: Το στιγμιότυπο Singleton είναι μια παγκόσμια κατάσταση, που καθιστά δύσκολη την παράλληλη εκτέλεση δοκιμών και την απομόνωση σεναρίων δοκιμών. Είναι δύσκολο να αντικαταστήσετε τις εξαρτήσεις του αντικειμένου Singleton.
  • Κρυφές εξαρτήσεις: Η χρήση του Singleton κρύβει τις εξαρτήσεις μεταξύ των modules, καθώς δεν περνιούνται ρητά.
  • Προβλήματα με πολυνηματικό περιβάλλον: Η ανάγκη συγχρονισμού της πρόσβασης στο στιγμιότυπο Singleton σε πολυνηματικό περιβάλλον μπορεί να οδηγήσει σε περιπλοκές και πιθανά σφάλματα.
  • Πολυπλοκότητα διαχείρισης: Η διαχείριση του κύκλου ζωής του αντικειμένου Singleton και η απομάκρυνσή του μπορεί να μην είναι προφανείς.

Αντί του Singleton, συχνά προτιμάται η χρήση εξαρτήσεων injection ή Service Locator για τη διαχείριση του κύκλου ζωής και της πρόσβασης στα αντικείμενα.

Παρακάτω παρατίθεται ένα τυπικό παράδειγμα υλοποίησης Singleton σε Swift που δείχνει τα αναφερόμενα προβλήματα:

// Singleton.swift
class DataManager {
    static let shared = DataManager() // Παγκόσμια σημείο πρόσβασης, κρύβει την εξάρτηση
    
    private init() { // Ιδιωτικός κατασκευαστής, δυσκολεύει τη δημιουργία mock αντικειμένων
        // Αρχικοποίηση πόρων ή κατάστασης
    }
    
    func fetchData() -> [String] {
        // Λογική λήψης δεδομένων (π.χ. από δίκτυο ή βάση δεδομένων)
        // Αυτή η λογική σχετίζεται με τη διαχείριση της κατάστασης και την πρόσβαση, παραβιάζοντας το SRP
        return ["Item 1", "Item 2"]
    }
}

// Παράδειγμα χρήσης
// Module που εξαρτάται άμεσα από το DataManager.shared
class ViewController {
    func displayData() {
        let data = DataManager.shared.fetchData() // Κρυφό dependency
        print(data)
    }
}

// Η δοκιμασία του ViewController γίνεται δύσκολη:
// Πώς να αντικαταστήσετε το DataManager.shared με ένα mock object;
// Πώς να απομονώσετε τις δοκιμές αν το DataManager.shared είναι παγκόσμιο;