This is a summary of the Martin Fowler's article about “Inversion of Control”, with the main example ported from Java to Python.
class Movie: def __init__(self, title, director): self.title = title self.director = director def __str__(self): return '%s, %s'%(self.title, self.director) class DummyMovieFinder: def __init__(self, filename): print 'Loading movies from: %s'%(filename) self.movies = ( Movie("Once Upon a Time in the West", "Sergio Leone"), Movie("Dances With Wolves", "Kevin Costner") ) def find_all(self): return self.movies class MovieLister: def __init__(self): self.finder = DummyMovieFinder("western_library.txt") def moviesDirectedBy(self, director): return [movie for movie in self.finder.find_all() if movie.director == director] if __name__ == '__main__': lister = MovieLister() movies = lister.moviesDirectedBy("Sergio Leone") print '\n'.join(map(str, movies))
The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?
The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.
Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.
So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.
class MovieLister: def __init__(self, finder): self.finder = finder def moviesDirectedBy(self, director): return [movie for movie in self.finder.find_all() if movie.director == director] class Assembler: def __init__(self): finder = DummyMovieFinder("western_library.txt") self.lister = MovieLister(finder) if __name__ == '__main__': a = Assembler() movies = a.lister.moviesDirectedBy("Sergio Leone") print '\n'.join(map(str, movies))
The assembler manage the information about which “MovieFinder” implementation should be injected in the “MovieLister” and which filename should be injected in the “MovieFinder”.
The centralization of all the dependencies in a single “Assembler” give the basis for the creation of a “Container”.
Usually, a container has the ability to load from a configuration file all the information about the wiring between the different components of a system.
In the case of this naive example:
import types class Container: def __init__(self, system_data): for component_name, component_class, component_args in system_data: if type(component_class) == types.ClassType: args = [self.__dict__[arg] for arg in component_args] self.__dict__[component_name] = component_class(*args) else: self.__dict__[component_name] = component_class if __name__ == '__main__': SYSTEM_DATA = ( ('filename', 'western_library.txt', None), ('finder', DummyMovieFinder, ('filename', )), ('lister', MovieLister, ('finder', )), ) c = Container(SYSTEM_DATA) movies = c.lister.moviesDirectedBy("Sergio Leone") print '\n'.join(map(str, movies))
Now we have a general purpose container with no dependencies to its components.