Sobes.tech
Middle+

Защо патърнят Singleton се счита за анти-патърн?

sobes.tech AI

Отговор от AI

Singleton нарушава принципите SOLID и усложнява тестването:

  • Нарушение на принципа за единствена отговорност (SRP): Класът отговаря едновременно за своята логика и за управлението на жизнения си цикъл (създаване и достъп до единствен екземпляр).
  • Нарушение на принципа за отвореност/затвореност (OCP): Разширяването на функционалността на Singleton класа може да бъде затруднено без промяна в неговия код.
  • Нарушение на принципа на подмяна на Барбара Лисков (LSP): Подтиповете на Singleton класа може да не удовлетворяват контрактите на базовия тип поради особеностите на реализацията на Singleton.
  • Нарушение на принципа за инверсия на зависимостите (DIP): Модулите зависят от конкретната реализация на Singleton, а не от абстракции. Това прави трудно заместването на Singleton обект с mock или stub за тестване.
  • Проблеми с тестването: Singleton екземплярът е глобално състояние, което усложнява паралелното изпълнение на тестове и изолацията на тестовите сценарии. Трудно е да се замести зависимостта от Singleton обект.
  • Скритите зависимости: Използването на Singleton скрива зависимостите между модулите, тъй като те не са предадени явно.
  • Проблеми с многоточковостта: Необходимостта от синхронизиране на достъпа до Singleton в многопоточна среда може да доведе до усложнения в кода и потенциални грешки.
  • Сложност при утилизация: Управлението на жизнения цикъл на Singleton обекта и неговата утилизация може да бъде неясна.

Вместо Singleton често е по-предпочитано да се използва внедряване на зависимости (Dependency 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"]
    }
}

// Пример за използване
// Модул, който директно зависи от DataManager.shared
class ViewController {
    func displayData() {
        let data = DataManager.shared.fetchData() // Скритата зависимост
        print(data)
    }
}

// Тестването на ViewController става по-трудно:
// Как да заместим DataManager.shared с mock обект?
// Как да изолираме тестовете, ако DataSourceManager.shared е глобален?