Как использовать шаблон синглтона в сочетании с внедрением зависимостей? - PullRequest
0 голосов
/ 03 апреля 2019

Недавно я слышал, что использование инъекций зависимостей является «единственным социально приемлемым способом использования синглтона в современном мире разработки программного обеспечения».Я не хочу прямо сейчас обсуждать точность этого утверждения, поскольку оно в основном основано на мнении.Моя цель сейчас - понять, как точно, как я могу использовать внедрение зависимостей с одноэлементным шаблоном.

Например, в моем последнем приложении для iOS у меня есть слой Service, где я храню свой URLSession код.Я создал этот слой как синглтон:

struct ServiceSingleton {

    private init()

    static let shared = ServiceSingleton()

    func fetchJSON() {
     // URLSession code
    }

}

Затем я использую shared в моем ViewController , как показано ниже:

class ViewController: UIViewController() {

    override viewDidLoad() {
        super.viewDidLoad()

        fetchData()    

    }

    fileprivate func fetchData() {

        ServiceSingleton.shared.fetchJSON()
    }

}

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

// Dependency Injection Constructor
override init(someProperty: SomePropertyType) {
    self.someProperty = someProperty
    super.init()
}

TL; DR:

(1) Не могли бы вы показать мне, как правильно использовать внедрение зависимостей с одноэлементным шаблоном в Swift?

(2) Не могли бы вы объяснить мне, чего это дает?

(3) Должен ли я всегда использовать DI, если теперь я использую шаблон синглтона в своих проектах iOS?

1 Ответ

4 голосов
/ 03 апреля 2019
  1. Не могли бы вы показать мне, как правильно использовать внедрение зависимостей с одноэлементным шаблоном в Swift?

    Вместо прямого доступа к ServiceSingleton.shared вы получаете доступ к переменной экземпляра, которая внедряется в ваш объект, обычно в инициализаторе, если это возможно, в противном случае в качестве настраиваемого свойства после инициализации:

    protocol FooService {
        func doFooStuff()
    }
    
    class ProductionFooService: FooService {
    
        private init() {}
    
        static let shared = ProductionFooService()
    
        func doFooStuff() {
            print("real URLSession code goes here")
        }
    
    }
    
    struct MockFooService: FooService {
        func doFooStuff() {
            print("Doing fake foo stuff!")
        }
    }
    
    class FooUser {
        let fooService: FooService
    
        init(fooService: FooService) { // "initializer based" injection
            self.fooService = fooService
        }
    
        func useFoo() {
            fooService.doFooStuff() // Doesn't directly call ProductionFooService.shared.doFooStuff
        }
    }
    
    let isRunningInAUnitTest = false
    
    let fooUser: FooUser
    if !isRunningInAUnitTest {
        fooUser = FooUser(fooService: ProductionFooService.shared) // In a release build, this is used.
    }
    else {
        fooUser = FooUser(fooService: MockFooService()) // In a unit test, this is used. 
    }
    
    fooUser.useFoo()
    

    Обычно инициализация ViewControllers выполняется вашими раскадровками, поэтому вы не можете внедрить свои зависимости через параметры инициализатора, и вместо этого вам придется использовать сохраненные свойства, которые устанавливаются после инициализации объекта.

  2. Не могли бы вы объяснить мне, чего это достигает?

    Ваш код больше не связан с ProductionFooService.shared. В результате вы можете представить различные реализации FooService, например, одну для бета-среды, пробную для модульного тестирования и т. Д.

    Если весь ваш код напрямую использует зависимости вашего продукта, вы ...

    1. обнаружите, что невозможно создать экземпляры ваших объектов в тестовой среде. Вам не нужно, чтобы ваши модульные тесты, среды тестирования CI, бета-среды и т. Д. Подключались к базам данных, сервисам и API API.

    2. Нет настоящих «юнит-тестов». Каждый тест будет проверять блок кода, а также все общие зависимости, от которых он транзитивно зависит. Если бы вы когда-нибудь внесли изменения в код для одной из этих зависимостей, это нарушило бы большинство модульных тестов в вашей системе, что затруднило бы точное определение того, что не удалось. Разъединяя ваши зависимости, вы можете использовать фиктивные объекты, которые делают минимум, необходимый для поддержки модульного теста, и гарантировать, что каждый тест проверяет только определенную единицу кода, а не транзитивные зависимости, на которые он опирается.

  3. Должен ли я всегда использовать DI при использовании шаблона Singleton в своих проектах iOS с этого момента?

    Это хорошая привычка. Конечно, есть проекты qucik-and-dirty-, для которых вы просто хотите быстро двигаться, и вам будет все равно, но вас удивит, сколько из этих предполагаемых проектов qucik-and-dirty-реально взлетят, и оплатить стоимость в будущем. Вам просто нужно знать, когда вы мешаете себе, не тратя дополнительное время, чтобы отделить ваши приличия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...