Is this dependency injection?

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

Interesting. Assume a Python system that works like this:

######################################################################
##
## DEMO
##
######################################################################

# ———————————————————————————
# Some python module defines a Bar component and states the dependencies
# We will assume that
# – Console denotes an object with a method WriteLine(string)
# – AppTitle denotes a string that represents the current application name
# – CurrentUser denotes a string that represents the current user name
#

class Bar(Component):
con = RequiredFeature(‘Console’, HasMethods(‘WriteLine’))
title = RequiredFeature(‘AppTitle’, IsInstanceOf(str))
user = RequiredFeature(‘CurrentUser’, IsInstanceOf(str))
def __init__(self):
self.X = 0
def PrintYourself(self):
self.con.WriteLine(‘– Bar instance –‘)
self.con.WriteLine(‘Title: %s’ % self.title)
self.con.WriteLine(‘User: %s’ % self.user)
self.con.WriteLine(‘X: %d’ % self.X)

# ———————————————————————————
# Some other python module defines a basic Console component
#

class SimpleConsole(Component):
def WriteLine(self, s):
print s

# ———————————————————————————
# Yet another python module defines a better Console component
#

class BetterConsole(Component):
def __init__(self, prefix=”):
self.prefix = prefix
def WriteLine(self, s):
lines = s.split(‘\n’)
for line in lines:
if line:
print self.prefix, line
else:
print

# ———————————————————————————
# Some third python module knows how to discover the current user’s name
#

def GetCurrentUser():
return os.getenv(‘USERNAME’) or ‘Some User’ # USERNAME is platform-specific

# ———————————————————————————
# Finally, the main python script specifies the application name,
# decides which components/values to use for what feature,
# and creates an instance of Bar to work with
#
if __name__ == ‘__main__’:
print ‘\n*** IoC Demo ***’
features.Provide(‘AppTitle’, ‘Inversion of Control …\n\n… The Python Way’)
features.Provide(‘CurrentUser’, GetCurrentUser)
features.Provide(‘Console’, BetterConsole, prefix=’–>’) # <-- transient lifestyle ##features.Provide('Console', BetterConsole(prefix='-->‘)) # <-- singleton lifestyle bar = Bar() bar.PrintYourself() # # Evidently, none of the used components needed to know about each other # => Loose coupling goal achieved
# ——————————————————————-

In the comments:

xixi haha

Maybe I misunderstood the article by Martin Flower. I think what you implemented is ServiceLocator pattern. Your implementation is really cool, especially the validator of a feature.

However, to me, the FeatureBroker is a kind of ServiceLocator. So it isn’t really the Inversion of control that I expected.

The reply:

Zoran Isailovski

Kind of Point of View. The essence of ServiceLocator is that components make explicit calls to the locator to request a particular feature, so every component “sees” the locator.

DependencyInjection is characterized by dependencies being somehow “declared” by components, while the resolution mechanism is completely hidden.

In this recipe, components only need to declare what they need. They do not need to see the FeatureBroker, since that is an “implementation detail” of the dependency declaration/resolution mechanism. (The implementation can be changed anytime to search classes for RequiredFeature slots and actually inject values, as you probably expected, without affecting existing components.) From this design-level point of view, the recipe conforms to DependencyInjection.

On the other hand, the implementation itself uses the FeatureBroker as a sort of ServiceLocator, so from an implementation-level point of view, the recipe is a ServiceLocator.

In the end, it all depends on whose lawyer you are. If you are an implementation lawyer, you will think of the recipe as being a ServiceLocator. If you are a design lawyer, you will think of it as DependencyInjection. And if you are management lawyer, you will think of it in terms like “Does it get the job done, and how much does it cost?”

…Also, statically typed languages do not support concepts like attribute descriptors, decorators etc. The strict separation of the two patterns is partly, if not mainly, enforced by the static nature of those languages.

In Python, thank to its dynamic features, it is possible to “adjust” how an attribute is retrieved and encapsulate that “adjustment” away from clients. It then becommes apparent that the two patterns are merely two manifestations of the same abstraction.

Post external references

  1. 1
    http://code.activestate.com/recipes/413268/
Source