Skip to content

Latest commit

 

History

History
135 lines (114 loc) · 10.3 KB

File metadata and controls

135 lines (114 loc) · 10.3 KB

Разрешение зависимостей

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

Давайте посмотри на то, а как же имея контейнер создать объект:

let cat1 = container.resolve() as Cat
let cat2: Cat = container.resolve()

Данный пример показывает, что для создания объекта достаточно всего лишь вызвать метод resolve и указать тип. При этом указание типа происходит не явно. Для сокращения записи был, придумал более оптимальный синтаксис:

let cat1 = *container as Cat
let cat2: Cat = *container

Да это оператор разыменования. На самом деле этот оператор написан в библиотеке и работает только с типом DIContainer, и он просто похож на оператор разыменования, причем как по виду, так и по смыслу.

По тегу

Если же нам нужно получить объект по типу и тегу можно воспользоваться следующим синтаксисом:

let cat1 = container.resolve(tag: YourTag.self) as Cat
let cat2: Cat = container.resolve(tag: YourTag.self)
let cat3: Cat = by(tag: YourTag.self, container.resolve())
let cat4: Cat = by(tag: YourTag.self, *container)

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

По имени

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

let cat1 = container.resolve(name: "yourName") as Cat
let cat2: Cat = container.resolve(name: "yourName")

Такой способ не поддерживает короткую запись, и не работает при внедрении зависимостей, за исключением внедрения через свойство - в этом случае можно указать имя. Настоятельно рекомендую воздержаться от соблазна использовать такую запись, так как она плохо подвержена рефакторингу, и при ее использовании легко опечататься.

Множественная

В случае если мы хотим получить все объекты, которые у нас есть в программе по некоторому типу, то можно воспользоваться следующим синтаксисом:

let cats1 = container.resolveMany() as [Cat]
let cats2: [Cat] = container.resolveMany()
let cats3: [Cat] = many(container.resolve())
let cats4: [Cat] = many(*container)

В этом случае будут получены все коды зарегистрированные в библиотеке. Как и в случае с тегом рекомендую использовать 4 запись, так как она работает и при внедрении зависимостей.

По умолчанию

В предыдущих примерах надо было обязательно указывать модификатор при разрешении зависимости. Теперь представим ситуацию, что у нас есть много объектов доступных по одному протоколу, у всех них есть имена, но мы хотим выделить один главный объект, который будет доступен просто по протоколу без модификатора. Мой любимый пример это логгер:

container.register{ FileLogger() }
.as(check: Logger.self, name: "File", {$0})
.lifetime(.single)

container.register{ ConsoleLogger() }
.as(check: Logger.self, name: "Console", {$0})
.lifetime(.single)

container.register{ OtherLogger() }
.as(check: Logger.self, name: "Other", {$0})
.lifetime(.single)

container.register{ MainLogger() }
.as(check: Logger.self, name: "Main", {$0})
.lifetime(.single)

В этом примере регистрируются три разных логера, каждый из которых доступен по своему имени. Но что если мы не хотим помнить имя, а хотим чтобы если мы не указали имени, то выдался определенный экземпляр? В принципе если в этом примере написать, за место последней регистрации вот так:

container.register{ MainLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

То мы добьёмся желаемого результата. И на этом можно было бы успокоиться, но давать имена каждому объекту, при условии, что эти объекты нам и не нужны сами по себе, может быть утомительно, и проще написать как то так:

container.register{ FileLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ ConsoleLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ OtherLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ MainLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

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

Собираем все вместе

Мы слегка ушли от темы "разрешение зависимостей", давайте еще раз запишем код регистрации наших логгеров:

container.register{ FileLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ ConsoleLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ OtherLogger() }
.as(check: Logger.self, {$0})
.lifetime(.single)

container.register{ MainLogger(loggers: many($0)) }
.as(check: Logger.self, {$0})
.lifetime(.single)
.default()

И теперь получим наш логгер:

let logger: Logger = *container

Всё, что было написано выше, является рабочим кодом, осталось разобраться, что же происходит. Вначале мы регистрируем три разных логгера. Предположим, что все они пишут логи в разные места. После мы регистрируем еще один логгер, но он является "по умолчанию" и более того, в методе инициализации принимает список всех логгеров в приложении. Обращаю внимание, из-за невозможности рекурсивной инициализации, будет передано только 3 логгера - самого себя он не передаст. Но если написать тоже самое, но внедрить логгеры через свойство, то будут переданы все 4 логгера.

Далее получаем объект по протоколу. Библиотека находит 4 компоненты, и пытается понять, а какую ей выбрать. Находит ту, которая помечена как "по умолчанию" и берет ее. После чего происходит создание объекта MainLogger.

Только что был продемонстрирован достаточно сложный кейс, но который очень лаконично записывается с использованием библиотеки.

Внедрение

В редких случаях, надо создать объект из кода, но при этом хочется внедрить все зависимости в этот объект. Для этого также как и всегда надо зарегистрировать этот тип в библиотеке, а потом для созданного объекта вызвать функцию inject(into:):

container.inject(into: yourObject)