Understanding Dependency inversion Principle Example


#1

Hey everyone,
Just here trying to understand the dependency inversion principle and how it actually relates to the ItemStore example in chapter 10. The book stated that by initializing the itemStore attribute in the app delegate instead of the view controller itself, we are removing dependencies. But I don’t quite see the difference.

In my mind this is how it goes.
If initialized inside of app delegate, you save the initialization code. However, if for whatever reason, we decided to not use itemStore as our data source, we still have to remove the declaration of var itemStore: ItemStore! inside of the viewController, and swap it out for a new data source attribute.

On the other-hand, if we decided to initialize the itemStore within the controller, when it comes time to swap it out for something else, the only additional work we are really doing is removing the attribute, swap it out for something else, and removing the initialization code. A bit more work, but not a lot more.

Perhaps this example is too small for me to see the significance. What are your perspectives?
Thanks in advance :slight_smile:


#2

Hey, @desertSky108
The pattern is usually called ether “Inversion of Control” (IoC) or “Dependency Injection” (DI). It is not so much about code evolution, it is usually about testing. The concept is that an object that knows how to create its own dependencies, knows too much. It knows the specific implementation on which it depends, instead of just the API. Practically, consider trying to test an object that creates its own, e.g., network layer dependency. There is no way to test such an object, without a network! It would be much easier to test if we could inject a mock.


#3

That’s an excellent way of describing it.

The implication is that reducing any form of dependency is a good thing.

Dependency = Object Creation + API

A simple example will help to demonstrate the idea.

struct Queue {
   init (size:Int, blocking:Bool)

   func enqueue (packet:Data) -> Bool
   func dequeue () -> Data?
}

Knowing about how to create an object is a form of dependency:

struct Jibber {
   // Knows about the details of creating a Queue
   var : Queue = Queue (1024, false)

   // Do something with the queue
   func jabber ()
}

The Jibber knows not only that the Queue has two methods, but also that creating it takes two arguments.

Dependency = Object Creation + API

That extra dependency can be eliminated by offloading the responsibility to create the Queue object onto the creator of Jibber:

struct Jibber {
   // Does not know about the details of creating a Queue
   var queue : Queue
   init (queue: Queue)

   // Do something with the queue
   func jabber ()
}

Now dependency is reduced to knowing just the API.

Dependency = API

Tester creates the Queue object:

struct Tester {
   func main () {
      var queue  = Queue (64, true)
      var jibber = Jibber (queue)
      jibber.jabber ()

      queue  = Queue (1024, false)
      jibber = Jibber (queue)
      jibber.jabber ()
   }
}

The above example can be scaled up to more complex scenarios.