Внедрение зависимостей для создания элегантных горизонтальных архитектур



Книга Внедрение зависимостей для создания элегантных горизонтальных архитектур

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


Рассмотрим систему из трех отдельных модулей:



  • Модуль A пользовательского интерфейса с SoundView.

  • Модуль B компонентный с SoundButtonView.

  • Модуль C логический с классом SoundService.


SoundView модуля A зависит в этом сценарии от SoundButtonView модуля B, а тот  —  от SoundService модуля C. Так формируется вертикальная цепочка модулей с заметным усилением зависимости верхних, таких как A, от нижних  —  B и C. Изменения в нижестоящем модуле чреваты нарушением функциональности вышестоящих, из-за такой многоуровневой структуры зависимостей время компиляции увеличивается, освоение системы усложняется:


Вертикальная архитектура

Горизонтальная структура с внедрением зависимостей  —  решение поэлегантнее. При таком расположении модуль A не «знает» о B или C, и наоборот.


Преимущества такого подхода:



  • Меньше время компиляции: перекомпилируется только модуль, подвергаемый изменениям.

  • Проще система: требуется глубокое понимание только конкретного модуля, с которым работают.

  • Независимость модуля: он легко отделяется от системы.

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


Горизонтальная структура строится так:



  • Создается один или несколько мостовых модулей DI с необходимыми протоколами, которые помещаются в модули A, B и C вместо конкретных реализаций:


// Расположение: модуль DI (несколько файлов)
protocol SoundViewProtocol { /* ... */ }
protocol SoundButtonViewProtocol { /* ... */ }
protocol SoundServiceProtocol { /* ... */ }


  • Ссылаясь на что-то из другого модуля, вместо конкретной реализации используем протокол:


// Расположение: модуль A
import DI

class SoundView: SoundViewProtocol {
var soundButtonView: SoundButtonViewProtocol?
// ...
}

// Расположение: модуль B
import DI

class SoundButtonView: SoundButtonViewProtocol {
var soundService: SoundServiceProtocol?
// ...
}

// Расположение: модуль C
import DI

class SoundService: SoundServiceProtocol {
// ...
}


  • На уровне приложения для разрешения зависимостей применяется локатор служб или инструмент вроде Swinject:


// Расположение: модуль DI
import Swinject

class AppDependencies {
let container = Container()

init() {
setupDependencies()
}

private func setupDependencies() {
container.register(SoundServiceProtocol.self) { _ in
SoundService()
}
container.register(SoundButtonViewProtocol.self) { r in
let service = r.resolve(SoundServiceProtocol.self)!
let buttonView = SoundButtonView()
buttonView.soundService = service
return buttonView
}
container.register(SoundViewProtocol.self) { r in
let buttonView = r.resolve(SoundButtonViewProtocol.self)!
let soundView = SoundView()
soundView.soundButtonView = buttonView
return soundView
}
}

func resolveSoundView() -> SoundViewProtocol {
return container.resolve(SoundViewProtocol.self)!
}
}


  • Зависимости в Swinject минимизируем созданием классов Container/Factory для каждого модуля, разрешая зависимости на уровне приложения:


// Расположение: модуль DI
protocol ModuleBFactoryProtocol {
func createSoundButton() -> SoundButtonProtocol
}

// Расположение: приложение
// Swinject предназначен исключительно для цели приложения. Поэтому модулям приложения
// импортировать его не нужно, чем облегчается будущее удаление или замена.
import Swinject

class ModuleBFactory {
func createSoundButton() -> SoundButtonProtocol {
let soundButton = container.resolve(SoundButtonProtocol.self) as! SoundButton
}
}

// Расположение: модуль A
import DI
// Точка входа — координатор, представление, контроллер и т. д.
class ModuleAEntryPoint {
// ...
init(moduleBFactory: ModuleBFactoryProtocol) {
self.moduleBFactory = moduleBFactory
} // ...
}

С этим изменением архитектура стала полностью горизонтальной, разработка эффективно оптимизирована при сохранении целостности и масштабируемости системы:




258   0  

Comments

    Ничего не найдено.