Паттерны: синглтоны против статических переменных и подходы к методам - PullRequest
0 голосов
/ 18 октября 2018

Я много читаю о синглтон-паттерне.В настоящее время я использую его для хранения групп глобального состояния в моем первом приложении.Я дошел до того, что мне интересно, какой подход для реализации клиентских классов API и т.те же проблемы?

Чтобы проиллюстрировать, что я имею в виду, я дважды пытался написать один и тот же сильно упрощенный и точно такой же (?) сценарий.

1.Синглтон, с которым работает контроллер представления:

struct APIClientSingletonClass {

    static let shared = APIClientSingletonClass()

    var stateOfSomehting: Bool = true
    var stateOfSomehtingMore: Bool = false
    var stateNumber: CGFloat = 1234
    var stateOfSomehtingComputed: CGFloat {
        return stateNumber * 10
    }

    func convertSomethingToSomethingElse() {
        // calling method in self like this:
        otherMethod()
    }
    func otherMethod() {
        // doing stuff here
    }
}



// Calling APIClient from outside:
class ViewControllerTalkingToSingleton: UIViewController {

    var api = APIClientSingletonClass.shared

    override func viewDidLoad() {
        super.viewDidLoad()
        api.convertSomethingToSomethingElse()
        api.stateOfSomehting = false
    }
}

2.Другой подход:

struct APIClientStruct {

    static var stateOfSomehting: Bool = true
    static var stateOfSomehtingMore: Bool = false
    static var stateNumber: CGFloat = 1234
    static var stateOfSomehtingComputed: CGFloat {
        return stateNumber * 10
    }

    static func convertSomethingToSomethingElse() {
        // calling method in self like this:
        APIClientStruct.otherMethod()
    }

    static func otherMethod() {
        // doing stuff here
    }
}


// Calling APIClient from outside:
class ViewControllerTalkingToStruct: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        APIClientStruct.convertSomethingToSomethingElse()
        APIClientStruct.stateOfSomehting = false
    }
}

Что вы, ребята, думаете?Подход 2 попадает в те же самые ловушки, которые, кажется, делают Синглтонов таким обоюдоострым мечом?

Любой вклад действительно приветствуется!Лучшее из Берлина

РЕДАКТИРОВАТЬ: Эта тема довольно интересная, но я не уверен, что это действительно относится к моему вопросу:

Разница между статическим классом и одноэлементным шаблоном?

Поскольку существует много точек зрения на эту тему, позвольте мне указать: Имеет ли мой подход 2 те же проблемы с тестированием и возможностью сопровождения кода?

Ответы [ 3 ]

0 голосов
/ 18 октября 2018

Что обычно делает тестирование синжетонов трудным, так это то, что к одноэлементным объектам обычно всегда обращаются напрямую.Из-за этого у вас нет возможности заменить реальный одноэлементный объект (например, хранилище данных, поддерживаемое базой данных) на фиктивный объект для тестирования (например, хранилище данных, которое поддерживается легко конфигурируемым массивомпредварительно определенные тестовые данные).

Использование статических элементов имеет ту же фундаментальную проблему.При непосредственном обращении к статическому члену у вас нет возможности заменить фиктивный объект вместо реальной реализации prod.

Решение этого довольно простое: не обращайтесь напрямую к одноэлементным элементам.Я делаю что-то вроде этого:

// An example of a dependency.
protocol DataAccessLayer {
    func getData() -> [Int]
}

// A real implementation of DataAccessLayer, backed by a real production database
class ProdDB: DataAccessLayer {
    static let instance = ProdDB()
    private init() {}

    func getData() -> [Int] {
        return [1, 2, 3] // pretend this actually queries a DB
    }
}

// A mcok implementation of DataAccessLayer, made for simple testing using mock data, without involving a production database.
class MockDB: DataAccessLayer {
    func getData() -> [Int] {
        return [1, 2, 3] // The mock *actually* hardcodes this data
    }
}


// A protocol that stores all databases and services used throughout your app
protocol ServiceContextProtocol {
    var dataAccessLayer: DataAccessLayer { get } // Present via protocol, either real impl or mock can go here
    //var fooAPIGateway: FooAPIGateway { get }
    //... add all other databases and services here
}

// The real service context, containing real databases and service gateways
class ProdServiceContext: ServiceContextProtocol {
    let dataAccessLayer: DataAccessLayer = ProdDB.instance
    //var fooAPIGateway: ProdFooAPIGateway { get }
    //... add all other prod databases and services here
}

// A mock service context, used in testing, which provides mocked databases and service gatways
class MockServiceContext: ServiceContextProtocol {
    let dataAccessLayer: DataAccessLayer  = MockDB()
    //var fooAPIGateway: MockFooAPIGateway { get }
    //... add all other mock databases and services here
}

let debug = false // Set this true when you're running in a test context

// A global variable through which you access all other global state (databases, services, etc.)
let ServiceContext: ServiceContextProtocol = debug ? MockServiceContext() : ProdServiceContext()

// Always reference ServiceContext.dataAccessLayer, ServiceContext.fooAPIGateway, etc.
// and *never* reference ProdDB.instance of MockDB directly.
0 голосов
/ 18 октября 2018

Синглтон на основе классов - это то, что нужно, при условии, что вы приспособитесь к внедрению зависимостей для своих тестов.Способ сделать это - создать отдельный синглтон для вашего приложения, который называется, скажем, DependencyManager.В вашем AppDelegate (или из других классов, если необходимо) вы создадите все контроллеры, сетевые службы, модели областей и т. Д., Которые вы хотите повесить на свой DependencyManager, а затем назначите их DependencyManager.Этот код будет пропущен вашими модульными тестами.

После этого ваши модульные тесты смогут получить доступ к DependencyManager (и, таким образом, создать экземпляр DependencyManager при первом доступе) и заполнить его фиктивными версиями этих контроллеров и служб в любой степени,модульный тест желает.

Ваши UIViewControllers, ваши модели MVVM-представления и т. д. могут обращаться к DependencyManager как к одиночке и, таким образом, получать либо реальные контроллеры и сервисы, либо фиктивную их версию, в зависимости от того,вы запускаете приложение или модульные тесты.

Если вы работаете с MVVM, я также рекомендую, чтобы когда UIViewController собирался создать свой класс модели представления, он сначала проверял специальное свойство в DependencyManager, чтобыпосмотрите, существует ли mockViewModel.Одно свойство может служить этой цели, так как только один из ваших UIViewControllers когда-либо будет проверен одновременно.Это свойство будет использоваться вместо создания новой модели представления для себя.Таким образом, вы можете смоделировать свои модели представлений при тестировании каждого UIViewController.(Существуют и другие приемы, связанные с возможностью поддержки одного UIViewController для тестирования, но я не буду здесь это освещать).

Обратите внимание, что все вышеперечисленное может очень хорошо работать с приложением, которое также хочетиспользуйте раскадровки и / или перья.Люди так расстроены из-за раскадровки, что не могут понять, как внедрить зависимости в макетные сервисы для своих контроллеров представлений.Что ж, решение выше!Просто убедитесь, что в вашем AppDelegate загружена раскадровка ПОСЛЕ настройки DependencyManager.(Удалите название раскадровки из вашего info.plist и создайте его самостоятельно в AppDelegate.),Я очень рекомендую подход!И обязательно напишите свои модульные тесты и тесты viewController либо во время, либо хотя бы сразу после разработки каждого такого класса, либо вы никогда не сможете их обойти!

0 голосов
/ 18 октября 2018

Я бы использовал синглтон на основе классов.Просто запомните 2 критерия наличия синглтона.Вы хотите GLOBAL ACCESS и SINGLE INSTANCE в вашей программе.Есть пара проблем, когда синглтон на основе структуры потерпит неудачу.После присвоения структуры новой переменной Swift делает полную копию под капотом.

Еще один полезный фрагмент информации можно найти по этой ссылке.

В чем разница междуСтруктурные и классовые синглтоны?

...