Заметки об IoC и DI

 
 

IoC - паттерн инверсии управления (Inversion of Control), почитать можно тут DI - паттерн внедрения зависимостей (Dependency Injection), почитать можно тут

В последнее время все чаще участвую в разного рода дискуссиях касательно архитектуры приложений, причем не важно каких - web, десктопных или мобильных. Во всех случаях всплывают примерно одни и те же проблемы и примерно одни и те же грабли. Дабы не “мозолить” себе язык и не объяснять свою точку зрения постоянно, решил написать эту статью. Тут рассмотрим некоторые основные подходы и паттерны и затем возможные их применения при разработке приложений.

Но для начала предлагаю рассмотреть небольшой примерчик для понимания того, откуда проблема берется.

protocol Inbox {
    func allLetters() -> [Mail]
}

class EmailInbox : Inbox { ... }

class MailService
{
    var inbox: Inbox = EmailInbox("null@example.com")

    func mail(with recipient: String) -> [Mail]
    {
        let letters = inbox.allLetters()
        return letters.filter { $0.recipient == recipient }
    }
}

Примерчик простой, однако даже в нем уже есть проблема - класс MailService крепко-накрепко связан с классом EmailInbox, несмотря даже на то, что мы честно выделили протокол Inbox. Теперь (например, в другом проекте) потребовалось использовать MailService, но при этом Inbox там реализован по другому (например через файлы). Это приведет к тому, что придется код переписывать, не получится просто взять и использовать.

Ситуацию можно немного ухудшить, написав примерно так.

class MailService
{
    func mail(with recipient: String) -> [Mail]
    {
        return EmailInbox.shared.allLetters().filter { $0.recipient == recipient }
    }
}

Теперь внешнему наблюдателю сразу даже будет непонятно, что внутри класса есть зависимость. Да при детальном анализе кода будет ясно, но если класс не из 10 строк, а из 1000? По этой же причине опасны singleton’ы.

Для обеспечения переносимости кода, для возможности организовать хорошее тестирование (например заmock’ать какие-нибудь зависимости) и т.д. с этим надо что-то делать. И вот тут-то и появляются разного рода умные слова, такие как Dependency Injection, Inversion of Control и иногда Service Locator. Как правило, считается что они значат примерно одно и то же, хотя если разбираться детальнее разница между ними есть и большая.

Самый первый и самый с одной стороны простой термин - Dependency Injection или по-русски “внедрение зависимостей”. Вообще, это комплекс мероприятий в результате которого зависимости оказываются в вашем объекте. Даже наш простой примерчик в общем делает dependency injection в самом простом его виде - зависимость просто создается. Внедрять зависимости можно спрашивая их у кого-то (см. Service Locator) или кто-то их нам установит автоматически (см. Inversion of Control).

let mailService = MailService(...)
let inbox = EmailInbox(...)

mailServie.inbox = inbox // dependency injection

Service Locator - это специальный объект (или фасад) у которого вы всегда сможете спросить нужную зависимость. Как правило, он либо знает где ее взять (на то он и locator), либо он сам хранит их у себя (см. паттерн реестр). Когда кому-то нужна какая-то зависимость, этот кто-то через механизмы singleton’а (или еще как) идет к Service Locator’у и спрашивает нужную зависимость.

class MailService
{
    var inbox: Inbox = ServiceLocator.shared.inbox // service locator
    ...
}

Третий термин (Inversion of Control) рассмотрим подробнее.

Но сначала давайте разберемся с тем, что за “control” инвертируется. Когда-то давно, когда приложения были консольными, control-flow приложения выглядел примерно так: мы запрашиваем с устройства ввода у пользователя данные, обрабатываем их и выводим на устройство вывода. Потоком управления управляло (пардон за тавталогию) само приложение. Когда появились графические UI приложение вместо того, чтобы управлять циклом приложения стало обрабатывать событие, отдав управление операционной системе. Таким образом получилась инверсия control-flow приложения. Таким же образом обстоит дело с инверсией при внедрении зависимостей при использовании паттерна Inversion of Control - управление временем жизни объекта и внедрением зависимостей делегируется специальному объекту, в задачу которого входит создание всех необходимых объектов и установка ссылок между ними. Этот специальный объект может называться сборщиком (assembler), контейнером (inversion of control container, см. Spring Framework) или еще как-нибудь, сути его это не меняеет.

class IoCContainer // Inversion of Control container (assembler)
{
    let mailService: MailService!
    let inbox: Inbox

    func build()
    {
        create()
        internlink()
        post()
    }

    private func create() {
        mailService = MailService()
        inbox = EmailInbox(...)
    }

    private func interlink() {
        mailService.inbox = inbox
    }

    private func post() {
        mailService.postCreate()
    }
}

Главное отличие контейнера от сервис локатора в том, что контейнер конкретный класс как правило не видит - зависимости для него появляются автомагически (automagically), а service locator видит и даже может быть хранит на него ссылку, а если еще и сильную… Кроме того, при использовании контейнера вы можете например иметь циклическую ссылку (с учетом конечно всех требований ARC и weak), или выполнить что-то после того, как весь граф (не дерево! и это важно) зависимостей будет собран (см. IoCContainer.post)

TODO в swift в Xcode

 
 

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

// TODO: we need to convert to float math for better precision
let sum = 1 + 2
let avg = sum / 2

И хотелось бы, чтобы Xcode каким-либо образом это подсвечивал, например как это было в старом добром Objective-C

#warning we need to convert to float math for better precision
int sum = 1 + 2;
int avg = sum / 2;

Но в Xcode “из коробки” увы нет такой поддержки для Swift. Быть может она появится в будущем, сейчас же предлагаю применить черную магию и написать скрипт. Для этого идем в Build Phases и создаем “Run Script”, поместить его можно в конце. Содержание скрипта такое:

TAGS="TODO:|FIXME:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

Xcode после сборки начнет подсвечивать ваши TODO’шки так

highlight todos

Если вы хотите подсвечивать еще например // ERROR: как ошибки, можно скрипт сделать таким

TAGS="TODO:|FIXME:"
ERRORTAG="ERROR:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$|($ERRORTAG).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/" | perl -p -e "s/($ERRORTAG)/ error: \$1/"

Xcode сделает так

highlight errors

Придумал не сам, нашел тут и перевел.

Yandex Mobile Contest

   
   

import Foundation

let ymc = Event("Yandex Mobile Contest", at:"2016-10-17")

let attendee = AnyPerson()
if attendee.haveInterest(in: "Мобильная разработка") {
    attendee.takePart(in: ymc) && attendee.winPrize(of: ymc)
}

Проводим в Яндексе соревнование мобильных разработчиков, приглашаем всех желающих. Регистрируйтесь тут

Почему не надо использовать React Native

 

Хорошая статья почему не надо использовать React Native, правда на английском.

https://arielelkin.github.io/articles/why-im-not-a-react-native-developer

GIT cookbook

 

Разное полезное для гита

Забрать к себе все ветки с remote

git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done
git fetch --all
git pull --all

Отправить все локальные бранчи в новый remote

git push REMOTE --all
# or git push REMOTE '*:*'

git push REMOTE --tags

Поудалять все теги по маске

git tag -l | grep _clog | while read remote; do git tag -d $remote; git push origin :refs/tags/$remote; done