Why use an IOC? (hint: testing)

Published by marco on

Inversion of Control Pattern

The IOC pattern is the [I] in SOLID. It stands for “Inversion of Control”.

In order to make good use of this pattern, an application should adhere to the following rules:

  • Prefers composition over inheritance, exposing clear dependencies
  • Refers to dependencies via interface or protocol types with as small a surface area as possible Obtains dependencies through injection, preferably in the constructor

Components built in this manner are agnostic in their implementation. They can be composed by an application as it sees fit.

Containers and Injection

Many projects will use an “IOC Container” that offers the following features for enforcing and benefiting from the pattern.

  • The container allows an application to register the class or object to use for a given interface
  • The application can also indicate whether the class or object should be considered a singleton
  • The container can create objects automatically, as long as all parameters to the object’s single constructor are of types registered with the container

We will first look at how composition even without a container is very powerful. Then we’ll look at how a container can improve on that.

Step One: A limited robot simulator

Let’s take a look at an example of an application that looks OK at first, but turns out not to be very flexible.

Note: The example is small, so some of the steps will feel like over­engineering. It’s a good point, but the principles shown here apply just as well for larger systems.

The following example defines a simulator that can move a robot along a route, defined by movements. The robot starts at a given location and can travel at a fixed speed.

enum Direction
{
  case north case south case east case west
}

struct Movement
{
  let direction: Direction
  let distance: Int 
}

struct Point
{
  var x: Int
  var y: Int
}

class FastRobot
{
  var speed = 2
  var location: Point = Point(x: 0, y: 0)
  let movements: [Movement] = [Movement(direction: .north, distance: 1)]

  func move()
  {
    for movement in movements
    {
      let distance = speed * movement.distance
      switch (movement.direction)
      {
        case .north:
          location.y += distance
        case .south: 
          location.y -= distance
        case .east:
          location.x += distance
        case .west:
          location.x -= distance
      }
    }
  }
}

class Simulator
{
  func run()
  {
    FastRobot().move()
  }
}

As mentioned above, this implementation looks well­written, but what if we wanted to verify that the robot ended up at the right location? Let’s try that below.

Step Two: Running the limited robot

Simulator().run()

// Now what?

It turns out that we can’t test anything in this application. We can fix this by applying the patterns outlined in the first section.

Step Three: Decouple the robot from the simulator

First, let’s tackle the Simulator’s interface:

class Simulator
{
  func run(robot: FastRobot)
  { 
    robot.move()
  } 
}

let robot = FastRobot() 
Simulator().run(robot: robot)

XCTAssertEqual(robot.location.x, 0)
XCTAssertEqual(robot.location.y, 2)

Now we can test that the robot is working as expected.

Still the robot is still quite hard­coded, as is the simulator’s relationship to the robot. The robot must be a FastRobot and it can only move along a fixed route.

Step Four: Reduce the robot “surface”

We’ll first decouple the Simulator from a direct dependence on the FastRobot.

protocol IRobot
{
  func move()
}

class Robot : IRobot
{
  // As above
}

class Simulator
{
  func run(robot: IRobot)
  {
    robot.move()
  }
}

Now the simulator only knows about the protocol

IRobot

, which has a very small surface area. It’s still too small to be very useful.

Step Five: Make the robot configurable

Instead of hard­coding everything, we can compose the robot out of parts. Examining the algorithm, we see three parts that could be externalized:

  • The robot’s speed is currently fixed. We could make a component that is responsible for calculating the speed of the robot. The robot’s route is also fixed. We could make a component to represent the route as well.
  • Finally, the robot’s initial position is also fixed. We could make that configurable as well.

Let’s first externalize all of the hard­coded values out of the FastRobot into a generic Robot class.

class Robot : IRobot
{
  let speed: Int
  var location: Point
  let movements: [Movement]

  init(speed: Int, location: Point, movements: [Movement])
  {
    self.speed = speed
    self.location = location
    self.movements = movements
  }

  func move()
  {
    for movement in movements
    {
      let distance = speed * movement.distance
      switch (movement.direction)
      {
        case .north:
          location.y += distance
        case .south: 
          location.y -= distance
        case .east: 
          location.x += distance
        case .west:
          location.x -= distance
      } 
    }
  } 
}

Now we can create a Robot, injecting all of the initial conditions.

let origin = Point(x: 0, y: 0)
let route = [Movement(direction: .north, distance: 1)]
let robot = Robot(speed: 2, location: origin, movements: route)

Simulator().run(robot: robot)
 
XCTAssertEqual(robot.location.x, 0)
XCTAssertEqual(robot.location.y, 2)

The same assertions hold as before, but the Robot class is much more generalized. We can now see how we could test the robot’s movement algorithm with various combinations of origin, speed and route.

At this point, we’ve made the robot and simulator composable and testable. Now we want to have a look at how we can separate the configuration from the usage.

Using a container to build objects

We’re not nearly done, though. What does this all have to do with a service provider? That’s where the inversion part comes in.

In the very first example, the Simulator was responsible for creating the robot. This made it impossible to test whether the robot did what it was supposed to do.

So we passed the robot in as a parameter to run(), making the caller responsible for creating the robot instead of the Simulator.

This is fine, as long as the caller is the top­level part of the program, responsible for composing the objects that will be used. However, what if the direct caller doesn’t know how to do that? Or, put another way, what if the caller should not be doing that?

What if the caller is a button handler in a UI? Would we want the button handler—or the UI that contains it—to be responsible for constructing the robot or its initial conditions?

This is where the container comes in: we want to register all of the objects and classes we want to use at the beginning of the application. This configuration can be retrieved at any later point without knowing any more than the interface that’s required.

This takes us full circle to the original code, except instead of creating the Simulator directly, we want to get it from a container, called a provider in the following examples.

let simulator = provider.resolve(ISimulator.self)

simulator.run()

let robot = provider.resolve(IRobot.self)

XCTAssertEqual(robot.location.x, 0)
XCTAssertEqual(robot.location.y, 2)

Note: For reasons of simplicity, we assume that all objects in the container are singletons.
 

Step Six: Configure the container

Let’s take the configurable code above and translate it to a container. Here the registrar is the configurable part and the provider is the part that can be used to retrieve objects based on that configuration. The registrar is sometimes called the composition root.

Note: We use the syntax for the Swift IOC, but the examples are hopefully clear enough in their intent.

In the example below, we register singletons for each of the objects we want the container to be able to create, Point, Int, [Movement], IRobot and Simulator.

let registrar = ServiceRegistrar()
  .registerSingle(Int.class) { _ in 2 }
  .registerSingle(Point.class) { _ in Point(x: 0, y: 0) }
  .registerSingle([Movement].class) { _ in [Movement(direction: .north, distance: 1)] }
  .registerSingle(IRobot.class) { p in Robot(
    speed: p.resolve(Int.class),
    location: p.resolve(Point.class),
    movements: p.resolve([Movement].class)
  }
  .registerSingle(Simulator.class) {p in Simulator(p.resolve(IRobot.class))}

This is a decent start, but many of the registrations above have no semantic meaning, like Int and Point and [Movement]. For these, it’s better to use higher­level abstractions.

Step Seven: using higher­level abstractions

We need to define three abstractions—called IOrigin, IRoute and IEngine—with implementations. As well, the IRobot interface needs to be redesigned to use them.

protocol IRoute
{
  var movements: [Movement] { get }
}

protocol IOrigin
{
  var point: Point { get }
}

protocol IEngine
{
  var speed: Int { get }
}

protocol ISimulator
{
  func run()
}

class Simulator : ISimulator
{
  var robot: IRobot
  init (_ robot: IRobot)
  {
    self.robot = robot
  }

  func run()
  { 
    robot.move()
  } 
}

struct StandardRoute : IRoute
{
  var movements: [Movement] = [Movement(direction: .north, distance: 1)]
}

struct StandardOrigin: IOrigin 
{
  var point: Point = Point(x: 0, y: 0) 
}

struct FastEngine : IEngine
{
  var speed: Int = 2
}

class Robot : IRobot
{
  var location: Point! 
  let engine: IEngine 
  let route: IRoute
  
  init(_ engine: IEngine, _ origin: IOrigin, _ route: IRoute)
  {
    self.engine = engine
    self.route = route

    location = origin.point 
  }

  func move()
  {
    for movement in movements
    {
      let distance = speed * movement.distance
      switch (movement.direction)
      {
        case .north:
          location.y += distance
        case .south: 
          location.y -= distance
        case .east: 
          location.x += distance
        case .west:
          location.x -= distance
      } 
    }
  } 
}

We’ve created concrete objects for our standard parameters. An added bonus of the improved semantics is that we can rewrite the init for IRobot so that it no longer expects argument labels—because the parameter are now clear without further explanation.

Now we can take another crack at the configuration using these new types. This time, we’ll define an extension of the IServiceRegistrar that we can use again below.

extension IServiceRegistrar
{
  func useSimulator() -> IServiceRegistrar
  {
    return self
      .registerSingle(IEngine.class) { _ in FastEngine() }
      .registerSingle(IOrigin.class) { _ in StandardOrigin() }
      .registerSingle(IRoute.class) { _ in StandardRoute() }
      .registerSingle(IRobot.class) { p in Robot(
        engine: p.resolve(IEngine.class),
        p.resolve(IOrigin.class), 
        p.resolve(IRoute.class))
      }
      .registerSingle(ISimulator.class) {p in Simulator(p.resolve(IRobot.class))}
  } 
}

We’ve now configured a system that knows how to create our simulator along with all of its dependencies. You can see that if the ISimulator type is resolved from the container, it will

  • create a Simulator, which
  • resolves the IRobot, which
  • resolves the IEngine, IOrigin and IRoute

Step Eight: Changing the speed

An application can now change the speed of the robot without knowing anything else about the simulator, simply by changing the IEngine that’s used.

class SlowEngine : IEngine
{
  var speed: Int = 1
}

let provider = ServiceRegistrar()
  .useSimulator()
  .registerSingle(IEngine.class) { _ in SlowEngine() }
  .commit()

As well, any location in the application can either use the IRobot or the ISimulator without having to know anything about how either of the concrete objects are constructed. The simulator might be much more complicated than the very simple one defined above. The robot might do much more when asked to move.

Step Nine: Using a factory

What if we wanted to let the robot decide how fast it is, depending on what kind of robot it is? Or what if we want to separate the speed from being fixed in the IEngine?

What we need is a way to create transient objects that require parameters that are not available in the provider. These are types like Int, String, etc., as we had in Step Six above.

The example below shows a very simple usage of the factory pattern. Instead of having a single IEngine for the whole application, we want to provide settings that the robot uses to get its engine.

The code below sketches the new types and shows how the robot would use them.

protocol IEngineFactory
{
  func createEngine(speed: Int)
}

protocol IRobotSettings
{
  var speed: Int
}

class Robot : IRobot
{
  init(_ engineFactory: IEngineFactory, _ settings: IRobotSettings, _ origin: IOrigin, _ route: IRoute)
  {
    self.engine = _engineFactory.createEngine(settings.speed)
    // … 
  }
}

You’ll note that we didn’t declare any new properties. The robot still just has an engine, but asks the factory to create it based on a speed, rather than having the provider inject its singleton.

Comments

2 Replies

#1 − casket

ashutoshweb3

Affordable Funeral Plans to match your needs. Learn about prepaid funeral plans, funeral plans cost, funeral plans for over 50s. Call +448081699459
order of service funeral,funeral order of service template,avalon funeral plans,cheap funeral plans,funeral plans,prepaid funeral plans,sun life funeral plan,funeral plans uk,compare funeral plans,age uk funeral plans,dignity funeral plans,dignity funerals,cheap funeral,co op funeral plans,co op funeral,cooperative funeral,top 10 funeral plans,safe hands funeral plans,monthly funeral plans,funeralcare,best funeral plans,prepaid funerals,cooperative funeral plans,golden leaves funeral plans,compare the market funeral plans,funeral plans cost,funeral plans for over 50s,funeral plans under 50,best prepaid funeral plans,cheapest prepaid funeral plans,pre paid funeral plans uk,funeral care plan,plan my funeral,british seniors funeral plan,how to plan a funeral,over 50 funeral plan,asda funeral plan,age concern funeral plan,post office funeral plan,cheapest funeral uk,funeral uk,co op funeral plan costs,co op funeral plan prices,coop prepaid funeral plans,which funeral plans,saga funeral plans,prepaid funeral plans compare,prepaid funeral plans age concern,go compare funeral plans,pay monthly funeral plans,perfect choice funeral plans,funeral plans scotland,funeral planning services,cheapest funeral plan uk,aviva funeral plan,plan your funeral,planning your own funeral,promis life funeral plan,50 plus funeral plans,age uk funeral plans reviews,are funeral plans a good idea,are prepaid funeral plans a good idea,best funeral plans uk,best value funeral plans,buy a funeral plan,choice funeral plans,co op funeralcare plans,co operative pre paid funeral plancompare funeral plans uk,cost of prepaid funeral plans,dignity funeral plans reviews,dignity pre paid funeral plans,funeral insurance plans uk,funeral plan companies,funeral plan prices,funeral plan providers,funeral plan quotes,funeral planning template,funeral planning trust,funeral plans expert,funeral plans for over 60s,funeral plans reviews,funeral pre planning,funeral protection plan,funeral savings plans,how do funeral plans work,how much is a funeral plan,how to plan a funeral for a loved one,how to plan a funeral uk,how to plan your own funeral,joint funeral plans,low cost funeral plans,lv funeral plan,online funeral planningover 60 funeral plan,planning a funeral uk,planning for burial,pre paid funeral plan providers,pre paid funeral plans age uk,pre paid funeral plans comparison,pre paid funeral plans reviews,pre paid funeral plans scotland,pre payment funeral plans,promis funeral plan,simple funeral plans,tesco funeral plan,the best funeral plan,the co op funeral plan,what is a funeral plan,which is the best funeral plan,funeral costs,help with funeral costs,funeral cost,how much does a funeral cost,average funeral cost,funeral costs uk,breakdown of funeral costs,average funeral cost uk,low cost funerals,how much is a funeral,prepaid funeral costs,how much does a funeral cost uk,how much does the average funeral cost,help with funeral costs uk,basic funeral cost,funeral prices,cheapest funeral costs,co op funeral costs,coop funeral costs,islamic funeral costs,help towards funeral costs,funeral costs cremation,funeral costs scotland,how much is a funeral cost,funeral packages,how much is a basic funeral,how much is the average funeral,average price of a funeral,what is the average cost of a funeral,average cost of a funeral cremation,how much is a funeral uk,funeral service,muslim funeral,memorial service,dignity funeral services,muslim funeral service,funeral service program,funeral service times,funeral services near me,catholic funeral service,funeral directors,local funeral directors,dignity funeral directors,funeral directors londonindependent funeral directors,funeral directors wakefield,funeral directors uk,cheap funeral directors,compare funeral directors,what do funeral directors do,funeral directors costs,best funeral directors,funeral insurance,funeral insurance for over 50s,funeral insurance uk,funeral insurance cover,funeral insurance plans,funeral insurance cost,co op funeral insurance,funeral expense insurance,promise life funeral insurance,funeral insurance policy,funeral insurance over 50,funeral insurance comparison,funeral insurance quote,funeral insurance plans uk,funeral life insurance,funeral cost insurance uk,best funeral insurance,coop funeral insurance,cheap funeral insurance,funeral insurance for under 50,seniors funeral insurance,post office funeral insurance,british seniors funeral insurance,funeral insurance for over 80,life insurance funeral plan,funeral directors insurance,humanist funeral,humanist funeral cost,humanist funeral service,non religious funeral,non religious funeral service,funeral car,hearse,funeral hearse,funeral procession,funeral hearse,funeral cars cost,funeral car hire cost,funeral hearse cost,funeral wreaths,funeral wreaths uk,funeral flower wreath,funeral wreath prices,how to make a funeral wreath,funeral wreath cost,wreath flower arrangements,dad wreath for funeral,floral wreath for funeral,funeral wreath ideas,funeral wreaths online,baby wreaths for funeral,butterfly funeral wreath,cheap funeral wreaths,cross wreaths for funerals,grandad wreaths funerals,round funeral wreath,sister funeral wreath,funeral homes,funeral homes near me,dignity funeral home,funeral home services,memorial funeral home,home funeral,home funeral services,catholic funeral homes,cheap funeral homes,cheap funeral homes near me,funeral cover,funeral cover plans,funeral cover uk,cheap funeral cover,cheap funeral cover for extended family,funeral arrangements,how to arrange a funeral,funeral arrangements checklist,funeral checklist,funeral planning checklist,funeral planning checklist,funeral arrangements checklist,funeral checklist,how to arrange a funeral checklist,how to plan your own funeral checklist,funeral preparation checklist,organising a funeral checklist,how to plan a funeral checklist,plan your own funeral checklist,pre planning funeral arrangements checklistpre planning funeral checklisthow to prepare for a funeral checklistmaking funeral arrangements checklist,funeral payment,funeral payment plans,funeral expenses payment,funeral expenses,help paying for a funeral,paying for a funeral,funeral allowance,paying for funeral in advance,paying for a funeral in installments,pre payment funeral plans,can you pay for funerals monthly,how to pay for a funeral,pay for my funeral,pay for your own funeral,paying for a funeral on benefits,cremation process,cremation services,cremation service,cremation,direct cremation,cremation jewelry,crematorium near me,direct cremation uk,cremation cost,co op direct cremation,cremation funeral,cheap cremation,how does cremation work,cremation ashes,how much is cremation,cremation uk,cremation memorial,simple cremation,how much does cremation cost,dignity cremation,cost of cremation uk,cheap cremation uk,catholic cremation,direct cremation near me,cremation prices,hindu cremation,prepaid direct cremation,average cost of burying cremated ashes,what is direct cremation,cheapest direct cremation,local crematorium,how long is a cremation service,direct cremation scotland,how much does a cremation cost uk,private cremation,cremation fees,how much does it cost to be cremated,cremations only,how much is a cremation uk,cremation planning,cremation plans,human cremation,average cost of cremation,direct cremation costs,direct cremation costs uk,how much does a cremation cost in england,low cost cremation,alternatives to cremation,christian cremation,cremation prices uk,direct disposal cremation,how much is a cremation funeral,how much is it to be cremated,jewish cremation,prepaid cremation,golden casket,casket,caskets for ashes,funeral caskets,wicker casket,casket sprays,what is a casket,cremation caskets,casket prices,american casket,wooden caskets,caskets uk,burial caskets,caskets for sale,baby caskets,casket coffin,difference between coffin and casket,casket or coffin,funeral caskets prices,wicker funeral caskets,casket cost,how much is a casket,a casket,batesville casket,metal casket,pink casket,black casket,oak casket,unusual coffins and caskets,caskets and urns,biodegradable caskets,casket coffin prices,white casket,cheap caskets,cardboard casket,wicker coffin,cardboard coffins,willow coffins,coffin prices,coffins for sale,how much does a coffin cost,white coffin,coffin cost,eco coffins,coffin prices uk,how much is a coffin,baby coffin,types of coffins and prices,coffins for sale uk,wicker coffin prices,wooden coffin,cheap coffins,cheap coffins for cremation,funeral coffin,funeral coffin flowers,coffin designs,types of coffins,how much is a coffin uk,buy a coffin,coffins direct,basket coffin,coffin box,cost of coffins uk,black coffin,eco friendly coffins,coffin maker,biodegradable coffin,coffins uk,compare the coffin,cheap coffins uk,casket coffin,cremation coffin,a coffin,coffin manufacturers,oak coffin,coffin company,creative coffins,lead coffin,funny coffins,cheapest coffin you can buy,pine coffin,coffin case,natural coffins,coffin supplier,american coffin,coffin for burials,how much is the cheapest coffin,custom coffins,coffin decor,buy coffin online,wicker coffin cremation,how much do wicker coffins cost,willow coffins prices,wooden coffins prices,cardboard coffin prices uk,unusual coffins and caskets,casket coffin prices,average coffin price,baby coffins prices,eco coffins prices,green coffins,metal coffin,unusual coffins,simple coffin,basic coffin,coffin shop,coffins online,what are coffins made of,coffin plans,old coffin,used coffins,budget coffins,eco coffins direct,printed coffins,define coffin,modern coffins,basket coffin cost,oak coffin prices,cheapest coffin prices,wicker basket coffin prices,cheap coffins for sale,wicker coffins for sale,cheap coffins for sale uk,wooden coffin box,funeral basket coffin,cheap coffins and caskets,casket covers coffins,coffin purchase,average cost of a coffin,buying a coffin direct,where can i buy a coffin,where to buy a coffin,cheap coffins online,low cost coffins,wholesale coffins,standard coffin,buy coffins online uk,coffin with glass lid,low price coffins,discount coffins,bury coffin,old style coffin,what is the cheapest coffin,lead lined coffin cost,pine box coffin cost,how much a coffin cost,coffin flowers white lilies,cheap wooden coffin,buy wooden coffin,coffin at funeral,burial ground,burial,woodland burial,natural burial ground,natural burial,burial service,jewish burial,green burial,woodland burial sites,burial place,woodland burial ground,funeral burial,burial costs,burial plot cost,burial plots,natural burial sites,green burial sites,burial vault,eco burial,cost of burial plot uk,average cost of burying cremated ashes,burial insurance,catholic burial,cost of burial uk,how much does a burial cost,how much does a burial plot cost,how much does a woodland burial cost,natural burial cost,planning for burial,catholic burial,burial band,burial burial,burial ceremony,burial uk,green burial ground,how much is a burial,jewish burial customs,burial insurance companies,best burial insurance companies,final expense insurance companies,pre burial insurance,burial insurance cost,burial insurance for parents,burial insurance for seniors,burial insurance plans,burial insurance quotes,burial insurance rates,burial insurance uk,how much is burial insurance,burial insurance with no waiting period,low cost burial insurance,burial life insurance,prepaid burial insurance,burial life insurance for seniors,burial life insurance policies,funeral invitation,jewish funeral,funeral parlor,hindu funeral,catholic funeral,funeral rites,funeral benefit,simple funerals,funeral booklet,funeral ceremony,woodland funeral,budget funerals,organising a funeral,green funeral,direct funeral,funeral sermons,chinese funeral,buddhist funeral,funeral program,christian funeral,rose funeral,funeral ideas,funeral words,funeral choice,funeral help,direct disposal funeral,masonic funeral,new orleans funeral,eco funeral,how to organise a funeral,japanese funeral,natural funeral