Inversion of Control (IoC)

Teagan Glenn

      Teagan42

Senior Software Engineer

Aetna

Inversion of Control

An abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming

Huh?

Inversion of Control

A chunk of code that uses, or consumes, another chunk of code is in control of the process, not implementation

Inversion of Control

Purpose

Decouple Your App

  • Dependency Contracts
  • Allows for interchangeable code
  • Encapsulation of modules

Facilitate Testing

  • Dependencies with assertions
  • Mock services
  • Skip over implementation
  • True unit testing

Inversion of Control

Dependencies

Constructor Injector

Dependencies are provided via constructor arguments

Example

interface ISettingsProvider {
    getSetting<SettingType>(settingKey: String): SettingType
}

class TestSettingsProvider : ISettingsProvider {
    getSetting<SettingType>(settingKey:String) =
        when(settingKey) {
            SETTING_IS_PRIVATE -> true
            SETTING_ACCOUNT_NAME -> "MyUser"
            SettING_SHARE_LOCATION -> false
        }
}

class AccountSettingsActivity(val settingsProvider: ISettingsProvider) {
    ...
    fun initialize() =
        settingsProvider.run {
            username = getSetting<String>(SETTING_ACCOUNT_NAME)
            privacy_toggle = getSetting<Boolean>(SETTING_IS_PRIVATE)
            share_location = getSettings<Boolean>(SETTING_SHARE_LOCATION)
        }
    ....
}

Property Injection

Dependencies are set via accessors

Example

interface ISettingsProvider {
    getSetting<SettingType>(settingKey: String): SettingType
}

class TestSettingsProvider : ISettingsProvider {
    getSetting<SettingType>(settingKey:String) =
        when(settingKey) {
            SETTING_IS_PRIVATE -> true
            SETTING_ACCOUNT_NAME -> "MyUser"
            SettING_SHARE_LOCATION -> false
        }
}

class AccountSettingsActivity() {
    var settingsProvider: ISettingsProvider? = null
    ...
    fun initialize() =
        settingsProvider?.run {
            username = getSetting<String>(SETTING_ACCOUNT_NAME)
            privacy_toggle = getSetting<Boolean>(SETTING_IS_PRIVATE)
            share_location = getSettings<Boolean>(SETTING_SHARE_LOCATION)
        }
    ....
}

Service Locator

Locates a service, usually by type or name

Example

interface IRESTServiceLocator {
    resolve(serviceClass: KClass<IRESTService>): IRestService
}

interface IRetrieveUserService : IRESTService {
    ...
}

class RetrieveUserService_v1 : IRetrieveUserService {
    getCurrentUser(): User?
    getUser(userId: Long): User?
}

class UserManager(val serviceLocator: IRESTServiceLocator) {
    fun setUsername(username: String) = 
        serviceLocator.resolve(IRetrieveUserService::class)
                      .getCurrentUser()
                      .setUsername(username)
    ...
}

Inversion of Control

Containers

A Container is...

A framework, of sorts, that can create a given type with all necessary dependencies

Roll Your Own

class PreferenceWrapper : IPreferenceProvider {
    constructor(sharedPreferences: SharedPreferences) {...}
    constructor(mockPreferences: MockPreferences) {...}

    fun getBoolean(prefName: String): Boolean { ... }
    fun getString(prefName: String): String { ... }
}

interface ISingletonScope {
    fun getApplicationContext(): Context
    fun getPreferences(): IPreferenceProvider
}

class ApplicationContainer(private val context: Context) 
        : ISingletonScope {
    private lateinit var preferences: SharedPreferences

    init {
        preferences = 
            PreferenceWrapper(
                PreferenceManager.getDefaultSharedPreferences(context))
    }

    fun getApplicationContext() = context

    fun getPreferences() = preferences
}

Roll Your Own

interface IConfigurationScope {
    fun getUserSettings(): UserSettings
    fun getPushNotificationSettings(): NotificationSettings
}

class PreferenceConfigurationContainer(private var singletons: ISingletonScope) 
        : IConfigurationScope {
    fun setSingletons(singletonScope: ISingletonScope) {
        singletons = singletonScope
    }

    fun getUserSettings() = with(singletons.getPreferences()) {
        UserSettings(getString(PREF_USERNAME), getString(PREF_PASSWORD))
    }

    fun getPushNotificationSettings() = with(singletons.getPreferences()) {
        NotificationSettings(getBoolean(PREF_PUSH_ENABLED), 
                             getBoolean(PREF_PUSH_VIBRATE), 
                             getString(PREF_PUSH_SOUND))
    }
}

Dagger

IoC Container Builder

Further Reading

Made with Slides.com