sumpfomat
sumpfomat

Reputation: 41

passing dependencies of dependencies using manual constructor injection in python

My Situation

I'm currently writing on a project in python which I want to use to learn a bit more about software architecture. I've read a few texts and watched a couple of talks about dependency injection and learned to love how clear constructor injection shows the dependencies of an object. However, I'm kind of struggling how to get a dependency passed to an object. I decided NOT to use a DI framework since:

  1. I don't have enough knowledge of DI to specify my requirements and thus cannot choose a framework.
  2. I want to keep the code free of more "magical" stuff since I have the feeling that introducing a seldom used framework drastically decreases readability. (More code to read of which only a small part is used).

Thus, I'm using custom factory functions to create objects and explicitly pass their dependencies:

    # Business and Data Objects
class Foo:
    def __init__(self,bar):
        self.bar = bar
    def do_stuff(self):
        print(self.bar)

class Bar:
    def __init__(self,prefix):
        self.prefix = prefix
    def __str__(self):
        return str(self.prefix)+"Hello"
    
# Wiring up dependencies
def create_bar():
    return Bar("Bar says: ")

def create_foo():
    return Foo(create_bar())

# Starting the application
f = create_foo()
f.do_stuff()

Alternatively, if Foo has to create a number of Bars itself, it gets the creator function passed through its constructor:

# Business and Data Objects
class Foo:
    def __init__(self,create_bar):
        self.create_bar = create_bar
    def do_stuff(self,times):
        for _ in range(times):
            bar = self.create_bar()
            print(bar)

class Bar:
    def __init__(self,greeting):
        self.greeting = greeting
    def __str__(self):
        return self.greeting
    
# Wiring up dependencies
def create_bar():
    return Bar("Hello World")

def create_foo():
    return Foo(create_bar)

# Starting the application
f = create_foo()
f.do_stuff(3)

While I'd love to hear improvement suggestions on the code, this is not really the point of this post. However, I feel that this introduction is required to understand

My Question

While the above looks rather clear, readable and understandable to me, I run into a problem when the prefix dependency of Bar is required to be identical in the context of each Foo object and thus is coupled to the Foo object lifetime. As an example consider a prefix which implements a counter (See code examples below for implementation details). I have two Ideas how to realize this, however, none of them seems perfect to me:

1) Pass Prefix through Foo

The first idea is to add a constructor parameter to Foo and make it store the prefix in each Foo instance.

The obvious drawback is, that it mixes up the responsibilities of Foo. It controls the business logic AND provides one of the dependencies to Bar. Once Bar does not require the dependency any more, Foo has to be modified. Seems like a no-go for me. Since I don't really think this should be a solution, I did not post the code here, but provided it on pastebin for the very interested reader ;)

2) Use Functions with State

Instead of placing the Prefix object inside Foo this approach is trying to encapsulate it inside the create_foo function. By creating one Prefix for each Foo object and referencing it in a nameless function using lambda, I keep the details (a.k.a there-is-a-prefix-object) away from Foo and inside my wiring-logic. Of course a named function would work, too (but lambda is shorter).

# Business and Data Objects
class Foo:
    def __init__(self,create_bar):
        self.create_bar = create_bar
    def do_stuff(self,times):
        for _ in range(times):
            bar = self.create_bar()
            print(bar)

class Bar:
    def __init__(self,prefix):
        self.prefix = prefix
    def __str__(self):
        return str(self.prefix)+"Hello"
        
class Prefix:
    def __init__(self,name):
        self.name = name
        self.count = 0
    def __str__(self):
        self.count +=1
        return self.name+" "+str(self.count)+": "
    
# Wiring up dependencies
def create_bar(prefix):
    return Bar(prefix)


def create_prefix(name):
    return Prefix(name)


def create_foo(name):
    prefix = create_prefix(name)
    return Foo(lambda : create_bar(prefix))

# Starting the application
f1 = create_foo("foo1")
f2 = create_foo("foo2")
f1.do_stuff(3)
f2.do_stuff(2)
f1.do_stuff(2)

This approach seems much more useful to me. However, I'm not sure about common practices and thus fear that having state inside functions is not really recommended. Coming from a java/C++ background, I'd expect a function to be dependent on its parameters, its class members (if it's a method) or some global state. Thus, a parameterless function that does not use global state would have to return exactly the same value every time it is called. This is not the case here. Once the returned object is modified (which means that counter in prefix has been increased), the function returns an object which has a different state than it had when beeing returned the first time. Is this assumption just caused by my restricted experience in python and do I have to change my mindset, i.e. don't think of functions but of something callable? Or is supplying functions with state an unintended misuse of lambda?

3) Using a Callable Class

To overcome my doubts on stateful functions I could use callable classes where the create_foo function of approach 2 would be replaced by this:

class BarCreator:
    def __init__(self, prefix):
        self.prefix = prefix
    def __call__(self):
        return create_bar(self.prefix)
     
def create_foo(name):
    return Foo(BarCreator(create_prefix(name)))

While this seems a usable solution for me, it is sooo much more verbose.

Summary

I'm not absolutely sure how to handle the situation. Although I prefer number 2 I still have my doubts. Furthermore, I'm still hope that anyone comes up with a more elegant way.

Please comment, if there is anything you think is too vague or can be possibly misunderstood. I will improve the question as far as my abilities allow me to do :) All examples should run under python2.7 and python3 - if you experience any problems, please report them in the comments and I'll try to fix my code.

Upvotes: 0

Views: 2967

Answers (1)

theodox
theodox

Reputation: 12208

If you want to inject a callable object but don't want it to have a complex setup -- if, as in your example, it's really just binding to a single input value -- you could try using functools.partial to provide a function <> value pair:

def factory_function(arg):
    #processing here
    return configurted_object_base_on_arg

class Consumer(object):
   def __init__(self, injection):
      self._injected = injection

   def use_injected_value():
      print self._injected()

injectable = functools.partial(factory_function, 'this is the configuration argument')
example = Consumer(injectable)
example.use_injected_value() # should return the result of your factory function and argument

As an aside, if you're creating a dependency injection setup like your option 3, you probably want to put the knwledge about how to do the configuration into a factory class rather than doing it inline as you're doing here. That way you can swap out factories if you want to choose between strategies. It's not functionally very different (unless the creation is more complex than this example and involves persistent state) but it's more flexible down the road if the code looks like

factory = FooBarFactory()
bar1 = factory.create_bar()
alt_factory = FooBlahFactory(extra_info)
bar2 = alt_factory.create_bar()

Upvotes: 1

Related Questions