An easy way of implementing the Dependency Injection Pattern in Python

Introduction

Dependency Injection is simply said, the idea that your classes should depend on abstraction, i.e. the abstraction of a datasource for example, rather than on concrete implementations. This means that classes will depend on interfaces rather than on real classes.

This has several advantages:

  1. It is easy to exchange on implementation for another, without changing the changing the client code.
  2. Because an interface usually covers a small part of the total API of a class, you can precisely determine which class gets access to which methods and functionality. If for example you have a datasource class, you can have a read- and a write-interface, and possible a combination of the two. Each client can then have either interface, depending on the need. This is an example of the principle of least privilege.

It looks like this:

The summary of this pattern is: you should depend on interfaces, not on concrete implementations. This make it easier to swap different implementations of the same functionality. This is also the ‘D’ in SOLID.

Implementation in Python

Because dependency-injection occurs in many application, like in many other languages, Python has packages to handle this pattern. Two of the possible many solutions are:

  • Dependency Injector: Dependency Injector is a dependency injection framework for Python.
  • Injector: Python dependency injection framework, inspired by Guice

Implementing Dependency Injection using these frameworks is something for another post. For now we will do this manually.

In this example I will build an imaginary carfactory which just needs wheels: big wheels and small wheels. Since the machines producing this wheels have the same interface, we can use that:

class WheelProducer:
def producewheel(self):
pass

For simplicity’s sake, our wheelproducer produces only a string representation of a wheel.

The CarFactory has an instance of a WheelProducer:

class CarFactory:

    def __init__(self,producer:WheelProducer):
        self._producer=producer

    def createwheel(self):
        return self._producer.producewheel()

Since the _producer field is private, a method to invoke the producer. Also note the use of typehints.

Notice that we define the WheelProducer field in terms of the interface, not of a concrete class.

Now on to our first WheelProducer:

class SmallWheelProducer(WheelProducer):

    def producewheel(self):
        return "small wheel"

This code is self-explanatory. Note that SmallWheelProducer implements the WheelProducer interface.

Next we do the same thing for the BigWheelProducer:

class BigWheelProducer(WheelProducer):


    def producewheel(self):
        return "big wheel"

Testing time

Testing this code is relatively easy:

if __name__=="__main__":
wheelproducer:WheelProducer=SmallWheelProducer()
factory=CarFactory(wheelproducer)
wheel=factory.createwheel()
print(wheel)

Line by line:

  1. We instatiate a SmallWheelProducer class. Notice that the typehint says WheelProducer, the interface (or rather the empty-class) type common to both SmallWheelProducer and BigWheelProducer.
  2. We pass that interface to the CarFactory constructor.
  3. Then we simply create a wheel, and print out the string representation

Conclusion

Implementing this pattern is not very difficult. The main thing to keep in mind is to use interfaces instead of concrete implementations.

In a next post, I will implement Dependency Injection using one or two of the packages mentioned earlier, and see how much easier that is, and how flexible it is.

Leave a Reply

Your email address will not be published. Required fields are marked *